diff --git a/Reporte_Vacaciones.xlsx b/Reporte_Vacaciones.xlsx new file mode 100644 index 0000000..8ce8134 Binary files /dev/null and b/Reporte_Vacaciones.xlsx differ diff --git a/package-lock.json b/package-lock.json index 293a43f..89b9798 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,11 +82,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -94,30 +96,32 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -133,27 +137,30 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7", + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -173,14 +180,15 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -295,29 +303,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -339,10 +347,11 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -420,27 +429,30 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -461,37 +473,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -693,12 +696,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1427,12 +1431,13 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", - "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1442,16 +1447,17 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz", - "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1461,12 +1467,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", - "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.24.7" + "@babel/plugin-transform-react-jsx": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1506,13 +1513,14 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz", - "integrity": "sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1839,33 +1847,32 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1874,14 +1881,14 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2341,9 +2348,10 @@ } }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", - "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz", + "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==", + "license": "BSD-3-Clause" }, "node_modules/@lit/react": { "version": "1.0.5", @@ -2357,6 +2365,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0" } @@ -2562,208 +2571,252 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz", - "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz", + "integrity": "sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz", - "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz", + "integrity": "sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz", - "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz", + "integrity": "sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz", - "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz", + "integrity": "sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz", + "integrity": "sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz", + "integrity": "sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz", - "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz", + "integrity": "sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz", - "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz", + "integrity": "sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz", - "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz", + "integrity": "sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz", - "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz", + "integrity": "sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz", - "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz", + "integrity": "sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz", - "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz", + "integrity": "sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz", - "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz", + "integrity": "sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz", - "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz", + "integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz", - "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz", + "integrity": "sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz", - "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz", + "integrity": "sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz", - "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz", + "integrity": "sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz", - "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz", + "integrity": "sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2848,10 +2901,11 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/minimist": { "version": "1.2.5", @@ -4684,9 +4738,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", - "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -4702,11 +4756,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001629", - "electron-to-chromium": "^1.4.796", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.16" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4920,9 +4975,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001640", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz", - "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==", + "version": "1.0.30001677", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", + "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", "dev": true, "funding": [ { @@ -4937,7 +4992,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "2.4.2", @@ -5521,10 +5577,11 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.820", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.820.tgz", - "integrity": "sha512-kK/4O/YunacfboFEk/BDf7VO1HoPmDudLTJAU9NmXIOSjsV7qVIX3OrI4REZo0VmdqhcpUcncQc6N8Q3aEXlHg==", - "dev": true + "version": "1.5.51", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.51.tgz", + "integrity": "sha512-kKeWV57KSS8jH4alKt/jKnvHPmJgBxXzGUSbMd4eQF+iOsVPl7bz2KUmu6eo80eMP8wVioTfTyTzdMgM15WXNg==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -5706,10 +5763,11 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -6850,15 +6908,16 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-parse-even-better-errors": { @@ -6963,19 +7022,21 @@ } }, "node_modules/lit-element": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.6.tgz", - "integrity": "sha512-U4sdJ3CSQip7sLGZ/uJskO5hGiqtlpxndsLr6mt3IQIjheg93UKYeGQjWMRql1s/cXNOaRrCzC2FQwjIwSUqkg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz", + "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==", + "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0", "@lit/reactive-element": "^2.0.4", - "lit-html": "^3.1.2" + "lit-html": "^3.2.0" } }, "node_modules/lit-html": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.4.tgz", - "integrity": "sha512-yKKO2uVv7zYFHlWMfZmqc+4hkmSbFp8jgjdZY9vvR9jr4J8fH6FUMXhr+ljfELgmjpvlF7Z1SJ5n5/Jeqtc9YA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz", + "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==", + "license": "BSD-3-Clause", "dependencies": { "@types/trusted-types": "^2.0.2" } @@ -7308,6 +7369,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -7316,10 +7378,11 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" }, "node_modules/normalize-package-data": { "version": "3.0.3", @@ -7613,9 +7676,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -7638,9 +7702,9 @@ } }, "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -7656,10 +7720,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -8099,12 +8164,13 @@ } }, "node_modules/rollup": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz", - "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.4.tgz", + "integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -8114,22 +8180,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.1", - "@rollup/rollup-android-arm64": "4.18.1", - "@rollup/rollup-darwin-arm64": "4.18.1", - "@rollup/rollup-darwin-x64": "4.18.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.1", - "@rollup/rollup-linux-arm-musleabihf": "4.18.1", - "@rollup/rollup-linux-arm64-gnu": "4.18.1", - "@rollup/rollup-linux-arm64-musl": "4.18.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1", - "@rollup/rollup-linux-riscv64-gnu": "4.18.1", - "@rollup/rollup-linux-s390x-gnu": "4.18.1", - "@rollup/rollup-linux-x64-gnu": "4.18.1", - "@rollup/rollup-linux-x64-musl": "4.18.1", - "@rollup/rollup-win32-arm64-msvc": "4.18.1", - "@rollup/rollup-win32-ia32-msvc": "4.18.1", - "@rollup/rollup-win32-x64-msvc": "4.18.1", + "@rollup/rollup-android-arm-eabi": "4.24.4", + "@rollup/rollup-android-arm64": "4.24.4", + "@rollup/rollup-darwin-arm64": "4.24.4", + "@rollup/rollup-darwin-x64": "4.24.4", + "@rollup/rollup-freebsd-arm64": "4.24.4", + "@rollup/rollup-freebsd-x64": "4.24.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.4", + "@rollup/rollup-linux-arm-musleabihf": "4.24.4", + "@rollup/rollup-linux-arm64-gnu": "4.24.4", + "@rollup/rollup-linux-arm64-musl": "4.24.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.4", + "@rollup/rollup-linux-riscv64-gnu": "4.24.4", + "@rollup/rollup-linux-s390x-gnu": "4.24.4", + "@rollup/rollup-linux-x64-gnu": "4.24.4", + "@rollup/rollup-linux-x64-musl": "4.24.4", + "@rollup/rollup-win32-arm64-msvc": "4.24.4", + "@rollup/rollup-win32-ia32-msvc": "4.24.4", + "@rollup/rollup-win32-x64-msvc": "4.24.4", "fsevents": "~2.3.2" } }, @@ -8416,10 +8484,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -8823,15 +8892,6 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -9084,9 +9144,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -9102,9 +9162,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -9320,9 +9381,9 @@ } }, "node_modules/vite-plugin-checker/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index 18cb231..209a580 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "dependencies": { "@f0rce/ace-widget": "1.0.2", - "@polymer/polymer": "3.5.1", + "@polymer/polymer": "3.5.2", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin/bundles": "24.5.1", "@vaadin/common-frontend": "0.0.19", @@ -19,30 +19,30 @@ "@vaadin/vaadin-usage-statistics": "2.1.3", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "2.29.3", - "lit": "3.1.4", + "lit": "3.2.1", "print-js": "1.6.0", "proj4": "2.12.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.23.1" + "react-router-dom": "6.26.2" }, "devDependencies": { - "@babel/preset-react": "7.24.7", + "@babel/preset-react": "7.25.7", "@preact/signals-react-transform": "0.4.0", - "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@vitejs/plugin-react": "4.3.1", - "async": "3.2.5", - "glob": "10.4.1", + "@rollup/plugin-replace": "6.0.1", + "@rollup/pluginutils": "5.1.2", + "@types/react": "18.3.11", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "4.3.3", + "async": "3.2.6", + "glob": "10.4.5", "rollup-plugin-brotli": "3.1.0", "rollup-plugin-visualizer": "5.12.0", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.4.5", - "vite": "5.3.3", - "vite-plugin-checker": "0.6.4", + "typescript": "5.6.3", + "vite": "5.4.9", + "vite-plugin-checker": "0.8.0", "workbox-build": "7.1.1", "workbox-core": "7.1.0", "workbox-precaching": "7.1.0" @@ -50,7 +50,7 @@ "vaadin": { "dependencies": { "@f0rce/ace-widget": "1.0.2", - "@polymer/polymer": "3.5.1", + "@polymer/polymer": "3.5.2", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin/bundles": "24.5.1", "@vaadin/common-frontend": "0.0.19", @@ -65,30 +65,30 @@ "@vaadin/vaadin-usage-statistics": "2.1.3", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "2.29.3", - "lit": "3.1.4", + "lit": "3.2.1", "print-js": "1.6.0", "proj4": "2.12.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.23.1" + "react-router-dom": "6.26.2" }, "devDependencies": { - "@babel/preset-react": "7.24.7", + "@babel/preset-react": "7.25.7", "@preact/signals-react-transform": "0.4.0", - "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@vitejs/plugin-react": "4.3.1", - "async": "3.2.5", - "glob": "10.4.1", + "@rollup/plugin-replace": "6.0.1", + "@rollup/pluginutils": "5.1.2", + "@types/react": "18.3.11", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "4.3.3", + "async": "3.2.6", + "glob": "10.4.5", "rollup-plugin-brotli": "3.1.0", "rollup-plugin-visualizer": "5.12.0", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.4.5", - "vite": "5.3.3", - "vite-plugin-checker": "0.6.4", + "typescript": "5.6.3", + "vite": "5.4.9", + "vite-plugin-checker": "0.8.0", "workbox-build": "7.1.1", "workbox-core": "7.1.0", "workbox-precaching": "7.1.0" diff --git a/pom.xml b/pom.xml index 1687fbf..dab210f 100644 --- a/pom.xml +++ b/pom.xml @@ -120,6 +120,11 @@ commons-beanutils 1.9.4 + + commons-io + commons-io + 2.17.0 + com.fasterxml.jackson.core jackson-core @@ -265,11 +270,39 @@ maven-surefire-plugin 3.3.1 + + io.github.git-commit-id + git-commit-id-maven-plugin + 9.0.1 + spring-boot:run + + io.github.git-commit-id + git-commit-id-maven-plugin + 9.0.1 + + + get-the-git-infos + + revision + + initialize + + + + true + ${project.build.outputDirectory}/git.properties + + ^git.build.(time|version)$ + ^git.commit.id.(abbrev|full)$ + + full + + org.apache.maven.plugins maven-checkstyle-plugin diff --git a/src/main/bundles/dev.bundle b/src/main/bundles/dev.bundle index 44ff38b..f938ac4 100644 Binary files a/src/main/bundles/dev.bundle and b/src/main/bundles/dev.bundle differ diff --git a/src/main/bundles/prod.bundle b/src/main/bundles/prod.bundle index 3338417..eae3a50 100644 Binary files a/src/main/bundles/prod.bundle and b/src/main/bundles/prod.bundle differ diff --git a/src/main/java/com/primefactorsolutions/config/PropertiesConfig.java b/src/main/java/com/primefactorsolutions/config/PropertiesConfig.java new file mode 100644 index 0000000..27be254 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/config/PropertiesConfig.java @@ -0,0 +1,19 @@ +package com.primefactorsolutions.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.io.ClassPathResource; + +@Configuration +public class PropertiesConfig { + @Bean + public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { + final PropertySourcesPlaceholderConfigurer propsConfig = new PropertySourcesPlaceholderConfigurer(); + propsConfig.setLocation(new ClassPathResource("git.properties")); + propsConfig.setIgnoreResourceNotFound(true); + propsConfig.setIgnoreUnresolvablePlaceholders(true); + + return propsConfig; + } +} diff --git a/src/main/java/com/primefactorsolutions/model/Actividad.java b/src/main/java/com/primefactorsolutions/model/Actividad.java deleted file mode 100644 index f7df96d..0000000 --- a/src/main/java/com/primefactorsolutions/model/Actividad.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.primefactorsolutions.model; - -public final class Actividad { - private String nombre; - private double lunes; - private double martes; - private double miercoles; - private double jueves; - private double viernes; - private double sabado; - private double domingo; - - public Actividad(final Builder builder) { - this.nombre = builder.nombre; - this.lunes = builder.lunes; - this.martes = builder.martes; - this.miercoles = builder.miercoles; - this.jueves = builder.jueves; - this.viernes = builder.viernes; - this.sabado = builder.sabado; - this.domingo = builder.domingo; - } - - public String getNombre() { - return nombre; - } - - public double getLunes() { - return lunes; - } - - public double getMartes() { - return martes; - } - - public double getMiercoles() { - return miercoles; - } - - public double getJueves() { - return jueves; - } - - public double getViernes() { - return viernes; - } - - public double getSabado() { - return sabado; - } - - public double getDomingo() { - return domingo; - } - - // Builder para crear instancias de Actividad - public static class Builder { - private String nombre; - private double lunes; - private double martes; - private double miercoles; - private double jueves; - private double viernes; - private double sabado; - private double domingo; - - public Builder nombre(final String nombre) { - this.nombre = nombre; - return this; - } - - public Builder lunes(final double horas) { - this.lunes = horas; - return this; - } - - public Builder martes(final double horas) { - this.martes = horas; - return this; - } - - public Builder miercoles(final double horas) { - this.miercoles = horas; - return this; - } - - public Builder jueves(final double horas) { - this.jueves = horas; - return this; - } - - public Builder viernes(final double horas) { - this.viernes = horas; - return this; - } - - public Builder sabado(final double horas) { - this.sabado = horas; - return this; - } - - public Builder domingo(final double horas) { - this.domingo = horas; - return this; - } - - public Actividad build() { - return new Actividad(this); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/primefactorsolutions/model/DocumentType.java b/src/main/java/com/primefactorsolutions/model/DocumentType.java index 96c8ce3..10251fc 100644 --- a/src/main/java/com/primefactorsolutions/model/DocumentType.java +++ b/src/main/java/com/primefactorsolutions/model/DocumentType.java @@ -1,36 +1,35 @@ package com.primefactorsolutions.model; public enum DocumentType { - All, - ID_CARD, - PAY_STUB, - PAY_SLIPS, - EMPLOYMENT_CONTRACT, - WORK_CERTIFICATES, + TODOS, + CARNET_DE_IDENTIDAD, + RECIBOS_DE_PAGO, + CONTRATO_DE_TRABAJO, + CERTIFICADO_DE_TRABAJO, NDA, - MEMORANDUMS, - CONTRACT_APPROVAL_MTEPS, - BACKGROUND_CHECK_CERTIFICATE, - PRE_EMPLOYMENT_EVALUATION, - INSURANCE_REGISTRATION_FORM, - INSURANCE_CANCELLATION_FORM, - PROFESSIONAL_DEGREE_1, - PROFESSIONAL_CERTIFICATE_1, - PROFESSIONAL_DEGREE_2, - PROFESSIONAL_CERTIFICATE_2, - PROFESSIONAL_DEGREE_3, - PROFESSIONAL_CERTIFICATE_3, - GENERAL_LABOR_REGULATIONS, - REMOTE_WORK_GUIDELINES, - SAFETY_REGULATIONS, - HUMAN_RESOURCES_GUIDELINES, - ADMINISTRATION_FUNCTIONS_MANUAL, - ENGINEERING_FUNCTIONS_MANUAL, - GENERAL_LABOR_LAW, - SUPREME_DECREE, - REGULATORY_RESOLUTION, - COMPLEMENTARY_REGULATION, - HEALTH_SAFETY_LAW, - INTERNSHIP_RULES, - OTHER + MEMORÁNDUMS, + APROBACIÓN_DE_CONTRATO_MTEPS, + CERTIFICADO_DE_ANTECEDENTES, + EVALUACIÓN_PRE_EMPLEO, + FORMULARIO_DE_INSCRIPCIÓN_AL_SEGURO, + FORMULARIO_DE_CANCELACIÓN_DE_SEGURO, + TÍTULO_PROFESIONAL_1, + CERTIFICACIÓN_PROFESIONAL_1, + TÍTULO_PROFESIONAL_2, + CERTIFICACIÓN_PROFECIONAL_2, + TÍTULO_PROFESIONAL_3, + CERTIFICACIÓN_PROFECIONAL_3, + NORMATIVA_LABORAL_GENERAL, + NORMAS_DE_TRABAJO_REMOTO, + NORMAS_DE_SEGURIDAD, + INSTRUCTIVOS_DE_RECURSOS_HUMANOS, + MANUAL_DE_FUNCIONES_DE_ADMINISTRACIÓN, + MANUAL_DE_FUNCIONES_DE_INGENIERÍA, + LEY_GENERAL_DEL_TRABAJO, + DECRETOS_SUPREMOS, + RESOLUCIONES_O_DISPOSICIONES_REGLAMENTARIAS, + NORMATIVA_COMPLEMENTARIA, + LEY_GRAL_DE_HIGIENE_SALUD_SEGURIDAD_OCUPACIONAL_Y_BIENESTAR, + NORMATIVA_REGLAMENTARIA_PARA_DESARROLLO_DE_PASANTÍAS, + OTROS } diff --git a/src/main/java/com/primefactorsolutions/model/Employee.java b/src/main/java/com/primefactorsolutions/model/Employee.java index 1d3df41..f4d3236 100644 --- a/src/main/java/com/primefactorsolutions/model/Employee.java +++ b/src/main/java/com/primefactorsolutions/model/Employee.java @@ -28,36 +28,42 @@ public class Employee extends BaseEntity implements UserDetails { @Pattern(regexp = "^[a-zA-Z ]+$", message = "El apellido solo debe contener letras") private String lastName; private LocalDate birthday; - @Pattern(regexp = "^[a-zA-Z ]+$", message = "La ciudad de nacimiento solo debe contener letras") + @Pattern(regexp = "^[a-zA-Z ,]+$", message = "La ciudad de nacimiento solo debe contener letras, espacios o comas") private String birthCity; private String age; - @Size(max = 100, message = "La dirección de residencia no debe exceder 100 caracteres") + @Size(max = 50, message = "La dirección de residencia no debe exceder 50 caracteres") private String residenceAddress; - @Size(max = 100, message = "La dirección local no debe exceder 100 caracteres") + @Size(max = 30, message = "La dirección local no debe exceder 100 caracteres") + @Pattern(regexp = "^[a-zA-Z -]+$", message = "La dirección local solo debe contener letras y guion") private String localAddress; @Pattern(regexp = "^[0-9]+$", message = "El número de teléfono debe contener solo números") private String phoneNumber; @Email(message = "El correo personal no tiene un formato válido") private String personalEmail; + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El cargo solo debe contener letras") private String position; @ManyToOne @JoinColumn(name = "team_id", nullable = false) private Team team; - @Size(max = 100, message = "El nombre de contacto de emergencia no debe exceder 100 caracteres") + + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El nombre y apellido de contacto" + + " de emergencia solo debe contener letras") private String emergencyCName; - @Size(max = 100, message = "La dirección de contacto de emergencia no debe exceder 100 caracteres") private String emergencyCAddress; - @Pattern(regexp = "^[0-9]+$", message = "El teléfono de contacto de emergencia debe contener solo números") + @Pattern(regexp = "^[0-9]+$", message = "El teléfono de contacto de emergencia " + + " debe contener solo números") private String emergencyCPhone; @Email(message = "El correo de contacto de emergencia no tiene un formato válido") private String emergencyCEmail; + + @Max(value = 10, message = "El número de hijos no puede exceder a 10") @Pattern(regexp = "^[0-9]+$", message = "La cantidad de hijos debe contener solo números") private String numberOfChildren; + @Pattern(regexp = "^[0-9]+$", message = "El CI debe contener solo números") private String ci; private String issuedIn; - @Size(max = 100, message = "El título no debe exceder 100 caracteres") private String pTitle1; private String pTitle2; private String pTitle3; @@ -70,9 +76,7 @@ public class Employee extends BaseEntity implements UserDetails { private String certification2; private String certification3; private String certification4; - @Size(max = 255, message = "El reconocimiento no debe exceder 255 caracteres") private String recognition; - @Size(max = 500, message = "Los logros no deben exceder 500 caracteres") private String achievements; private String language; diff --git a/src/main/java/com/primefactorsolutions/model/HoursWorked.java b/src/main/java/com/primefactorsolutions/model/HoursWorked.java index d02310f..888b5fb 100644 --- a/src/main/java/com/primefactorsolutions/model/HoursWorked.java +++ b/src/main/java/com/primefactorsolutions/model/HoursWorked.java @@ -1,41 +1,56 @@ package com.primefactorsolutions.model; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; -import java.util.UUID; +import java.time.LocalDate; +import java.time.temporal.WeekFields; +import java.util.List; +import java.util.Locale; +@Data @Entity +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) public class HoursWorked extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - @ManyToOne + @JoinColumn(name = "employee_id", nullable = true) private Employee employee; + @ManyToOne + @JoinColumn(name = "team_id", nullable = true) + private Team team; + private int weekNumber; + private LocalDate date; + private String actividad; + private String tareasEspecificas; + private double hours; + private double horasTareasEspecificas; + private double horaspendientes; private double totalHours; - public HoursWorked() { } - - public UUID getId() { - return id; + public static double calculateTotalHours(final List activities) { + return activities.stream() + .mapToDouble(activity -> activity.hours + activity.horasTareasEspecificas) + .sum(); } - public void setId(final UUID id) { - this.id = id; + public static double calculatePendingHours(final List activities) { + double totalHoursWorked = calculateTotalHours(activities); + return Math.max(0, 40 - totalHoursWorked); } public Employee getEmployee() { return employee; } - public void setEmployee(final Employee value) { - this.employee = value; + public void setEmployee(final Employee employee) { + this.employee = employee; } public int getWeekNumber() { @@ -45,12 +60,90 @@ public class HoursWorked extends BaseEntity { public void setWeekNumber(final int weekNumber) { this.weekNumber = weekNumber; } + public LocalDate getDate() { + return date; + } + + public void setDate(final LocalDate date) { + this.date = date; + if (date != null) { + WeekFields weekFields = WeekFields.of(Locale.getDefault()); + this.weekNumber = date.get(weekFields.weekOfWeekBasedYear()); + } + } + + public String getActividad() { + return actividad; + } + + public void setActividad(final String actividad) { + this.actividad = actividad; + } + + public double getHours() { + return hours; + } + + public void setHours(final double hours) { + this.hours = hours; + } public double getTotalHours() { - return totalHours; + double total = this.getHours(); + return totalHours + total; } public void setTotalHours(final double totalHours) { this.totalHours = totalHours; } + + public Team getTeam() { + return team; + } + + public void setTeam(final Team team) { + this.team = team; + } + + public String getTareasEspecificas() { + return tareasEspecificas; + } + + public void setTareasEspecificas(final String tareasEspecificas) { + this.tareasEspecificas = tareasEspecificas; + } + + public double getHorasTareasEspecificas() { + return horasTareasEspecificas; + } + + public void setHorasTareasEspecificas(final double horasTareasEspecificas) { + this.tareasEspecificas = tareasEspecificas; + } + public double getHoraspendientes() { + //double horasTrabajadas = this.getTotalHours() + this.getHorasTareasEspecificas(); + return 40; + } + + public void setHoraspendientes(final double horaspendientes) { + this.horaspendientes = horaspendientes; + } + + public double getHoursWorked() { + return hours; + } + + public void setHoursWorked(final double hoursWorked) { + this.hours = hoursWorked; + } + + public double getTotalHoursWorked() { + return totalHours; + } + + public void setTotalHoursWorked(final double totalHoursWorked) { + this.totalHours = totalHoursWorked; + } + + } diff --git a/src/main/java/com/primefactorsolutions/model/TimeOffRequestStatus.java b/src/main/java/com/primefactorsolutions/model/TimeOffRequestStatus.java index 6f0a2d0..3c8d1e2 100644 --- a/src/main/java/com/primefactorsolutions/model/TimeOffRequestStatus.java +++ b/src/main/java/com/primefactorsolutions/model/TimeOffRequestStatus.java @@ -10,7 +10,4 @@ public enum TimeOffRequestStatus { VENCIDO, SOLICITADO, - EN_REVISION, - COMPLETADO, - CANCELADO, } diff --git a/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java b/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java index 3016f8e..3dad742 100644 --- a/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java +++ b/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java @@ -3,6 +3,7 @@ package com.primefactorsolutions.repositories; import com.primefactorsolutions.model.Employee; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -10,4 +11,7 @@ public interface EmployeeRepository extends JpaRepository { Optional findByUsername(String username); Optional findByPersonalEmail(String personalEmail); -} + Optional findByTeamId(UUID teamId); + + List findByTeamName(String teamName); +} \ No newline at end of file diff --git a/src/main/java/com/primefactorsolutions/repositories/HoursWorkedRepository.java b/src/main/java/com/primefactorsolutions/repositories/HoursWorkedRepository.java index e5ad85b..8662eb2 100644 --- a/src/main/java/com/primefactorsolutions/repositories/HoursWorkedRepository.java +++ b/src/main/java/com/primefactorsolutions/repositories/HoursWorkedRepository.java @@ -2,9 +2,15 @@ package com.primefactorsolutions.repositories; import com.primefactorsolutions.model.HoursWorked; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface HoursWorkedRepository extends JpaRepository { - // Puedes definir consultas personalizadas aquí si es necesario. +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + + +public interface HoursWorkedRepository extends JpaRepository { + List findByWeekNumber(int weekNumber); + List findByDate(LocalDate date); + List findByEmployeeIdAndWeekNumber(UUID employeeId, int weekNumber); } + diff --git a/src/main/java/com/primefactorsolutions/service/EmployeeService.java b/src/main/java/com/primefactorsolutions/service/EmployeeService.java index e63c88a..07b9ec4 100644 --- a/src/main/java/com/primefactorsolutions/service/EmployeeService.java +++ b/src/main/java/com/primefactorsolutions/service/EmployeeService.java @@ -48,6 +48,13 @@ public class EmployeeService { return null; } + public String getTeamLeadName(final UUID teamId) { + // Encuentra al empleado con el rol de lead_manager en el equipo especificado + Optional leadManager = employeeRepository.findByTeamId(teamId); + + return leadManager.map(employee -> employee.getFirstName() + " " + employee.getLastName()) + .orElse("No asignado"); + } public List findEmployees( final int start, final int pageSize, final String sortProperty, final boolean asc) { List employees = employeeRepository.findAll(); @@ -115,4 +122,8 @@ public class EmployeeService { public List findAllEmployees() { return employeeRepository.findAll(); } + + public List findEmployeesByTeam(final String teamName) { + return employeeRepository.findByTeamName(teamName); + } } \ No newline at end of file diff --git a/src/main/java/com/primefactorsolutions/service/HoursWorkedService.java b/src/main/java/com/primefactorsolutions/service/HoursWorkedService.java index 2da3c4b..36395bf 100644 --- a/src/main/java/com/primefactorsolutions/service/HoursWorkedService.java +++ b/src/main/java/com/primefactorsolutions/service/HoursWorkedService.java @@ -2,10 +2,12 @@ package com.primefactorsolutions.service; import com.primefactorsolutions.model.HoursWorked; import com.primefactorsolutions.repositories.HoursWorkedRepository; +import org.apache.commons.beanutils.BeanComparator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.List; +import java.time.LocalDate; +import java.util.*; @Service public class HoursWorkedService { @@ -20,6 +22,20 @@ public class HoursWorkedService { return hoursWorkedRepository.findAll(); } + public double getTotalHoursWorkedByEmployeeForWeek(final UUID employeeId, final int weekNumber) { + List hoursWorkedList = hoursWorkedRepository.findByWeekNumber(weekNumber); + return hoursWorkedList.stream() + .filter(hw -> hw.getEmployee().getId().equals(employeeId)) + .mapToDouble(HoursWorked::getTotalHours) + .sum(); + } + + public HoursWorked findHoursWorked(final UUID id) { + Optional hoursWorked = hoursWorkedRepository.findById(id); + HoursWorked hw = hoursWorked.get(); + return hw; + } + public HoursWorked saveHoursWorked(final HoursWorked hoursWorked) { return hoursWorkedRepository.save(hoursWorked); } @@ -28,8 +44,56 @@ public class HoursWorkedService { return hoursWorkedRepository.save(hoursWorked); } - public void deleteHoursWorked(final Long id) { - hoursWorkedRepository.deleteById(id); + public double getTotalHoursForEmployee(final UUID employeeId, final int weekNumber) { + List activities = hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber); + return HoursWorked.calculateTotalHours(activities); } -} + public double getPendingHoursForEmployee(final UUID employeeId, final int weekNumber) { + List activities = hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber); + return HoursWorked.calculatePendingHours(activities); + } + + public List findByWeekNumber(final int weekNumber) { + return hoursWorkedRepository.findByWeekNumber(weekNumber); + } + + public List findByDate(final LocalDate date) { + return hoursWorkedRepository.findByDate(date); + } + + public List findByDateAndWeekNumber(final LocalDate date, final int weekNumber) { + return hoursWorkedRepository.findByDate(date); + } + + public List findHoursWorkeds( + final int start, final int pageSize, final String sortProperty, final boolean asc) { + List hoursWorkeds = hoursWorkedRepository.findAll(); + + int end = Math.min(start + pageSize, hoursWorkeds.size()); + hoursWorkeds.sort(new BeanComparator<>(sortProperty)); + + if (!asc) { + Collections.reverse(hoursWorkeds); + } + + return hoursWorkeds.subList(start, end); + } + + public List findHoursWorkeds(final int start, final int pageSize) { + List hoursWorkeds = hoursWorkedRepository.findAll(); + + int end = Math.min(start + pageSize, hoursWorkeds.size()); + return hoursWorkeds.subList(start, end); + } + + public HoursWorked getHoursWorked(final UUID id) { + final Optional hoursWorked = hoursWorkedRepository.findById(id); + return hoursWorked.get(); + } + + public List findListHoursWorkedEmployee(final UUID employeeId, final int weekNumber) { + return hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber); + } + +} diff --git a/src/main/java/com/primefactorsolutions/service/ReportService.java b/src/main/java/com/primefactorsolutions/service/ReportService.java index beaa21a..d7e2e3f 100644 --- a/src/main/java/com/primefactorsolutions/service/ReportService.java +++ b/src/main/java/com/primefactorsolutions/service/ReportService.java @@ -11,6 +11,7 @@ import lombok.SneakyThrows; import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; @@ -31,20 +32,59 @@ public class ReportService { // Este método ahora solo crea el archivo Excel a partir de los datos que recibe. public byte[] writeAsExcel(final String reportName, final List headers, - final List> data) + final List> data, final String selectedTeam, + final int weekNumber, final int currentYear) throws IOException { - return createExcelFile(reportName, headers, data); + return createExcelFile(reportName, headers, data, selectedTeam, weekNumber, currentYear); } private byte[] createExcelFile(final String reportName, final List headers, - final List> data) + final List> data, final String selectedTeam, + final int weekNumber, final int currentYear) throws IOException { try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream os = new ByteArrayOutputStream()) { Sheet sheet = workbook.createSheet(reportName); // Crear encabezados - Row headerRow = sheet.createRow(0); + // Crear una fila para el rótulo "Reporte por equipo" + Row titleRow = sheet.createRow(0); // Fila 0 para el rótulo + Cell titleCell = titleRow.createCell(0); + + // Concatenar el nombre del equipo al rótulo + String titleText = "Informe: " + weekNumber + "/" + currentYear; + titleCell.setCellValue(titleText); + + // Estilo del rótulo + CellStyle titleStyle = workbook.createCellStyle(); + Font titleFont = workbook.createFont(); + titleFont.setBold(true); + titleFont.setFontHeightInPoints((short) 14); // Tamaño de la fuente + titleStyle.setFont(titleFont); + titleCell.setCellStyle(titleStyle); + + // Fusionar celdas para el rótulo + sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1)); // Ajusta el rango de celdas + + // Crear filas adicionales con la información solicitada + Row asuntoRow = sheet.createRow(1); // Fila 1: Asunto + asuntoRow.createCell(0).setCellValue("Asunto: Informe semanal de horas trabajadas"); + sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, headers.size() - 1)); + + Row semanaRow = sheet.createRow(2); // Fila 2: Semana + semanaRow.createCell(0).setCellValue("Semana: " + weekNumber); // Puedes insertar una fecha real aquí + sheet.addMergedRegion(new CellRangeAddress(2, 2, 0, headers.size() - 1)); + + Row horasCumplirRow = sheet.createRow(3); // Fila 3: Horas a cumplir + horasCumplirRow.createCell(0).setCellValue("Horas a cumplir: 40 horas"); // Puedes agregar las horas reales + sheet.addMergedRegion(new CellRangeAddress(3, 3, 0, headers.size() - 1)); + + Row teamLeadRow = sheet.createRow(4); // Fila 4: Team Lead + teamLeadRow.createCell(0).setCellValue("Team Lead: "); // Solo texto + sheet.addMergedRegion(new CellRangeAddress(4, 4, 0, headers.size() - 1)); + + // Crear encabezados (fila 5) + Row headerRow = sheet.createRow(5); // Los encabezados empiezan en la fila 5 CellStyle headerStyle = workbook.createCellStyle(); Font headerFont = workbook.createFont(); headerFont.setBold(true); @@ -56,20 +96,22 @@ public class ReportService { cell.setCellStyle(headerStyle); } - // Crear filas de datos + // Crear filas de datos (a partir de la fila 6) for (int i = 0; i < data.size(); i++) { - Row dataRow = sheet.createRow(i + 1); + Row dataRow = sheet.createRow(i + 6); // Los datos empiezan después de la fila de encabezados Map rowData = data.get(i); int cellIndex = 0; for (String key : headers) { Cell cell = dataRow.createCell(cellIndex++); Object value = rowData.get(key); - switch (value) { - case String s -> cell.setCellValue(s); - case Number number -> cell.setCellValue(number.doubleValue()); - case null -> cell.setCellValue(""); // Manejo de valores nulos - default -> { + if (value != null) { + if (value instanceof String) { + cell.setCellValue((String) value); + } else if (value instanceof Number) { + cell.setCellValue(((Number) value).doubleValue()); } + } else { + cell.setCellValue(""); // Manejo de valores nulos } } } @@ -132,4 +174,4 @@ public class ReportService { return cfg; } -} +} \ No newline at end of file diff --git a/src/main/java/com/primefactorsolutions/views/AssessmentsListView.java b/src/main/java/com/primefactorsolutions/views/AssessmentsListView.java index cf755c3..88160c6 100644 --- a/src/main/java/com/primefactorsolutions/views/AssessmentsListView.java +++ b/src/main/java/com/primefactorsolutions/views/AssessmentsListView.java @@ -37,7 +37,7 @@ public class AssessmentsListView extends Main { final HorizontalLayout hl = new HorizontalLayout(); final Button addAssessment = new Button("Add Assessment"); addAssessment.addClickListener((ComponentEventListener>) buttonClickEvent -> { - this.getUI().get().navigate(AssessmentView.class, "new"); + this.getUI().flatMap(ui -> ui.navigate(AssessmentView.class, "new")); }); hl.add(addAssessment); @@ -51,7 +51,7 @@ public class AssessmentsListView extends Main { grid.addComponentColumn((ValueProvider) assessment -> { var result = new Button("Result", event -> - this.getUI().get().navigate(SubmissionView.class, assessment.getId().toString()) + this.getUI().flatMap(ui -> ui.navigate(SubmissionView.class, assessment.getId().toString())) ); result.setEnabled(assessment.isCompleted()); @@ -95,6 +95,7 @@ public class AssessmentsListView extends Main { return assessmentService.getAssessments().size(); } + @SuppressWarnings("unused") @Override public Stream fetch(final Query query) { int limit = query.getLimit(); diff --git a/src/main/java/com/primefactorsolutions/views/BaseView.java b/src/main/java/com/primefactorsolutions/views/BaseView.java new file mode 100644 index 0000000..48a17d8 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/views/BaseView.java @@ -0,0 +1,16 @@ +package com.primefactorsolutions.views; + +import com.vaadin.flow.component.html.Main; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import lombok.Getter; + +@Getter +public class BaseView extends Main { + + private final VerticalLayout currentPageLayout; + + public BaseView() { + currentPageLayout = new VerticalLayout(); + add(currentPageLayout); + } +} diff --git a/src/main/java/com/primefactorsolutions/views/CandidatesListView.java b/src/main/java/com/primefactorsolutions/views/CandidatesListView.java index 599bb57..c887c37 100644 --- a/src/main/java/com/primefactorsolutions/views/CandidatesListView.java +++ b/src/main/java/com/primefactorsolutions/views/CandidatesListView.java @@ -28,15 +28,13 @@ import java.util.stream.Stream; @Route(value = "/candidates", layout = MainLayout.class) @PermitAll public class CandidatesListView extends Main { - private final CandidateService candidateService; public CandidatesListView(final CandidateService candidateService) { - this.candidateService = candidateService; final HorizontalLayout hl = new HorizontalLayout(); final Button addCandidate = new Button("Add Candidate"); addCandidate.addClickListener((ComponentEventListener>) buttonClickEvent -> { - this.getUI().get().navigate(CandidateView.class, "new"); + this.getUI().flatMap(ui -> ui.navigate(CandidateView.class, "new")); }); hl.add(addCandidate); @@ -46,7 +44,7 @@ public class CandidatesListView extends Main { grid.addComponentColumn((ValueProvider) candidate -> { final Button edit = new Button("Edit"); edit.addClickListener((ComponentEventListener>) buttonClickEvent -> - this.getUI().get().navigate(CandidateView.class, candidate.getId().toString())); + this.getUI().flatMap(ui -> ui.navigate(CandidateView.class, candidate.getId().toString()))); return edit; }); @@ -61,6 +59,7 @@ public class CandidatesListView extends Main { return candidateService.getCandidates().size(); } + @SuppressWarnings("unused") @Override public Stream fetch(final Query query) { int limit = query.getLimit(); diff --git a/src/main/java/com/primefactorsolutions/views/Constants.java b/src/main/java/com/primefactorsolutions/views/Constants.java new file mode 100644 index 0000000..92d0410 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/views/Constants.java @@ -0,0 +1,5 @@ +package com.primefactorsolutions.views; + +public class Constants { + public static final int PAGE_SIZE = 10; +} diff --git a/src/main/java/com/primefactorsolutions/views/DocumentView.java b/src/main/java/com/primefactorsolutions/views/DocumentView.java index 49c44a3..b1bbad9 100644 --- a/src/main/java/com/primefactorsolutions/views/DocumentView.java +++ b/src/main/java/com/primefactorsolutions/views/DocumentView.java @@ -39,9 +39,9 @@ import java.io.InputStream; @PageTitle("Document") @Route(value = "/documents/:documentId?/:action?", layout = MainLayout.class) public class DocumentView extends BeanValidationForm implements HasUrlParameter { - private final TextField fileName = new TextField("Document Name"); - private final ComboBox documentType = new ComboBox<>("Document Type"); - private final ComboBox employeeComboBox = new ComboBox<>("Employee"); + private final TextField fileName = new TextField("Nombre del documento"); + private final ComboBox documentType = new ComboBox<>("Tipo de documento"); + private final ComboBox employeeComboBox = new ComboBox<>("Empleado"); private final MemoryBuffer buffer = new MemoryBuffer(); private final Upload uploadButton = new Upload(buffer); private final DocumentService documentService; @@ -68,19 +68,19 @@ public class DocumentView extends BeanValidationForm implements HasUrl } protected Button createSaveButton() { - saveButton = new Button("Save"); + saveButton = new Button("Guardar"); saveButton.addClickListener(event -> saveDocument()); return saveButton; } protected Button createCloseButton() { - Button closeButton = new Button("Close"); + Button closeButton = new Button("Salir"); closeButton.addClickListener(event -> closeForm()); return closeButton; } protected Button createViewDocumentButton() { - viewDocumentButton = new Button("View Document"); + viewDocumentButton = new Button("Ver documento"); viewDocumentButton.setEnabled(false); viewDocumentButton.addClickListener(event -> viewDocument()); return viewDocumentButton; @@ -130,7 +130,7 @@ public class DocumentView extends BeanValidationForm implements HasUrl ui.getPage().open(registration.getResourceUri().toString()); }); } catch (IOException e) { - Notification.show("Error reading file."); + Notification.show("Error al leer el archivo."); } } @@ -148,10 +148,10 @@ public class DocumentView extends BeanValidationForm implements HasUrl setDocumentCreator(document); documentService.saveDocument(document); - Notification.show("File saved successfully."); + Notification.show("Archivo guardado correctamente."); clearForm(); } else { - Notification.show("Save failed: Please complete all fields and upload a file."); + Notification.show("Error al guardar: Por favor, complete todos los campos y cargue un archivo."); } } @@ -179,7 +179,7 @@ public class DocumentView extends BeanValidationForm implements HasUrl try { return buffer.getInputStream().readAllBytes(); } catch (IOException e) { - Notification.show("Error reading file data."); + Notification.show("Error al leer los datos del archivo."); return new byte[0]; } } @@ -220,13 +220,13 @@ public class DocumentView extends BeanValidationForm implements HasUrl uploadButton.setAcceptedFileTypes(".pdf"); uploadButton.addSucceededListener(event -> { fileUploaded = true; - Notification.show("File uploaded successfully."); + Notification.show("Archivo cargado correctamente."); viewDocumentButton.setEnabled(true); updateSaveButtonState(); }); uploadButton.getElement().addEventListener("file-remove", event -> { fileUploaded = false; - Notification.show("File removed."); + Notification.show("Archivo eliminado."); viewDocumentButton.setEnabled(false); updateSaveButtonState(); }); diff --git a/src/main/java/com/primefactorsolutions/views/DocumentsListView.java b/src/main/java/com/primefactorsolutions/views/DocumentsListView.java index c95e917..6e247fb 100644 --- a/src/main/java/com/primefactorsolutions/views/DocumentsListView.java +++ b/src/main/java/com/primefactorsolutions/views/DocumentsListView.java @@ -9,8 +9,8 @@ import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.grid.GridSortOrder; -import com.vaadin.flow.component.html.Main; import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.data.provider.SortDirection; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; @@ -24,13 +24,14 @@ import org.vaadin.firitin.components.grid.PagingGrid; import java.io.ByteArrayInputStream; import java.util.List; +import static com.primefactorsolutions.views.Constants.PAGE_SIZE; @SpringComponent @Scope("prototype") @PageTitle("Documents") @Route(value = "/documents", layout = MainLayout.class) @PermitAll -public class DocumentsListView extends Main { +public class DocumentsListView extends BaseView { private final DocumentService documentService; private final EmployeeService employeeService; @@ -46,16 +47,24 @@ public class DocumentsListView extends Main { } private void initializeView() { + getCurrentPageLayout().add(createActionButton("Añadir documento", this::navigateToAddDocumentView)); + + final HorizontalLayout hl = new HorizontalLayout(); + hl.add(createDocumentTypeFilter()); + hl.add(createEmployeeFilter()); + + getCurrentPageLayout().add(hl); + configureDocumentGrid(); - add(createActionButton("Add Document", this::navigateToAddDocumentView)); - add(createDocumentTypeFilter()); - add(createEmployeeFilter()); - add(documentGrid); + getCurrentPageLayout().add(documentGrid); } private void configureDocumentGrid() { documentGrid.setColumns("fileName", "documentType", "creator"); - documentGrid.addComponentColumn(this::createEmployeeSpan).setHeader("Employee"); + documentGrid.getColumnByKey("fileName").setHeader("Nombre archivo"); + documentGrid.getColumnByKey("documentType").setHeader("Tipo"); + documentGrid.getColumnByKey("creator").setHeader("Creador"); + documentGrid.addComponentColumn(this::createEmployeeSpan).setHeader("Empleado"); addActionColumns(); configurePagination(); } @@ -67,9 +76,9 @@ public class DocumentsListView extends Main { } private void addActionColumns() { - addDocumentActionColumn("View", this::navigateToDocumentView); - addDocumentActionColumn("Edit", this::navigateToEditDocumentView); - addDocumentActionColumn("Download", this::downloadDocument); + addDocumentActionColumn("Ver", this::navigateToDocumentView); + addDocumentActionColumn("Editar", this::navigateToEditDocumentView); + addDocumentActionColumn("Descargar", this::downloadDocument); } private void addDocumentActionColumn(final String label, final DocumentActionHandler handler) { @@ -83,7 +92,7 @@ public class DocumentsListView extends Main { } private ComboBox createDocumentTypeFilter() { - documentTypeFilter = new ComboBox<>("Document Type"); + documentTypeFilter = new ComboBox<>("Tipo de documento"); documentTypeFilter.setItems(DocumentType.values()); documentTypeFilter.setValue(DocumentType.values()[0]); documentTypeFilter.addValueChangeListener(event -> { @@ -93,7 +102,7 @@ public class DocumentsListView extends Main { } private ComboBox createEmployeeFilter() { - employeeFilter = new ComboBox<>("Employee"); + employeeFilter = new ComboBox<>("Empleado"); List employees = employeeService.findAllEmployees(); employees.addFirst(createAllEmployeesOption()); employeeFilter.setItems(employees); @@ -107,12 +116,13 @@ public class DocumentsListView extends Main { private Employee createAllEmployeesOption() { Employee allEmployeesOption = new Employee(); - allEmployeesOption.setFirstName("All"); + allEmployeesOption.setFirstName("TODOS"); return allEmployeesOption; } private String getEmployeeLabel(final Employee employee) { - return employee.getFirstName().equals("All") ? "All" : employee.getFirstName() + " " + employee.getLastName(); + return employee.getFirstName().equals("TODOS") + ? "TODOS" : employee.getFirstName() + " " + employee.getLastName(); } private void navigateToEditDocumentView(final Document document) { @@ -133,7 +143,7 @@ public class DocumentsListView extends Main { private void configurePagination() { documentGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM); - documentGrid.setPageSize(5); + documentGrid.setPageSize(PAGE_SIZE); } private void updateDocumentGrid(final DocumentType documentType, final Employee employee) { diff --git a/src/main/java/com/primefactorsolutions/views/EmployeeView.java b/src/main/java/com/primefactorsolutions/views/EmployeeView.java index f0c9599..4e34abb 100644 --- a/src/main/java/com/primefactorsolutions/views/EmployeeView.java +++ b/src/main/java/com/primefactorsolutions/views/EmployeeView.java @@ -1,5 +1,7 @@ package com.primefactorsolutions.views; +import com.primefactorsolutions.model.Employee; +import com.primefactorsolutions.model.Team; import com.primefactorsolutions.model.*; import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.ReportService; @@ -54,7 +56,6 @@ public class EmployeeView extends BeanValidationForm implements HasUrl private final TimeOffRequestService requestService; private final TeamService teamService; - // TODO: campo usado para registrar al empleado en LDAP. Este campo podria estar en otro form eventualmente. private final TextField username = createTextField("Username: ", 30, true); private final TextField firstName = createTextField("Nombres: ", 30, true); @@ -63,24 +64,21 @@ public class EmployeeView extends BeanValidationForm implements HasUrl private final ComboBox gender = createGenderComboBox(); private final VDatePicker birthday = new VDatePicker("Fecha de Nacimiento"); private final TextField age = createTextField("Edad", 3, false); - private final TextField birthCity = createTextField("Ciudad y País de Nacimiento", 20, false); + private final TextField birthCity = createTextField("Ciudad y País de Nacimiento ejemplo: (Ciudad, País) ", + 30, false); private final TextField residenceAddress = createTextField("Dirección de Residencia", 50, false); - private final TextField localAddress = createTextField("Dep/Provincia de Residencia", 10, false); + private final TextField localAddress = createTextField("Departamento y Provincia de Residencia " + + " ejemplo: (Departamento-Provincia)", 30, false); private final ComboBox maritalStatus = createMaritalStatusComboBox(); - private final TextField numberOfChildren = createTextField("Numero de Hijos", 3, false); - private final TextField ci = createTextField("CI", 30, false); - private final TextField issuedIn = createTextField("Expedido en ", 30, false); + private final TextField numberOfChildren = createTextField("Numero de Hijos", 2, false); + private final TextField ci = createTextField("CI", 10, false); + private final TextField issuedIn = createTextField("Expedido en ", 10, false); private final TextField phoneNumber = createTextField("Teléfono", 8, false); - private final EmailField personalEmail = createEmailField("E-mail"); - private final TextField cod = createTextField("Codigo de Empleado", 30, false); - private final TextField position = createTextField("Cargo", 30, false); - private final ComboBox team = new ComboBox<>("Equipo"); - private final TextField leadManager = createTextField("Lead/Manager", 30, false); - private final TextField project = createTextField("Proyecto", 30, false); + private final EmailField personalEmail = createEmailField("E-mail ejemplo: (ejemplo@gmail.com)"); private final TextField emergencyCName = createTextField("Nombres y Apellidos de Contacto", 50, false); private final TextField emergencyCAddress = createTextField("Dirección de Contacto", 50, false); private final TextField emergencyCPhone = createTextField("Teléfono de Contacto", 8, false); - private final EmailField emergencyCEmail = createEmailField("Email de Contacto"); + private final EmailField emergencyCEmail = createEmailField("Email de Contacto ejemplo: (ejemplo@gmail.com)"); private final MemoryBuffer buffer = new MemoryBuffer(); private final Upload upload = new Upload(buffer); @@ -99,10 +97,15 @@ public class EmployeeView extends BeanValidationForm implements HasUrl private final TextField certification4 = createTextField("Certificación 4", 30, false); private final TextField recognition = createTextField("Reconocimientos", 30, false); private final TextField achievements = createTextField("Logros Profesionales", 30, false); - private final TextField language = createTextField("Idioma", 30, false); + private final TextField language = createTextField("Idioma", 50, false); private final TextField languageLevel = createTextField("Nivel de Idioma", 30, false); - //INFORMACION DE CONTRATACION + //INFORMACION ADMINISTRATIVA + private final TextField cod = createTextField("Codigo de Empleado", 20, false); + private final TextField position = createTextField("Cargo", 30, false); + private final ComboBox team = new ComboBox<>("Equipo"); + private final TextField leadManager = createTextField("Lead/Manager", 30, false); + private final TextField project = createTextField("Proyecto", 30, false); private final VDatePicker dateOfEntry = new VDatePicker("Fecha de Ingreso"); private final VDatePicker dateOfExit = new VDatePicker("Fecha de Retiro"); private final TextField contractType = createTextField("Tipo de Contratación", 30, false); @@ -139,7 +142,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl //TITULOS PARA INFORMACIÓN ADMINISTRATIVA private final H2 infoAdm = new H2("Información Administrativa"); private final H3 infoCont = new H3("Información de Contratación"); - private final H3 datBanc = new H3("Datos Bancados"); + private final H3 datBanc = new H3("Datos Bancarios"); private final H3 datGest = new H3("Datos Gestora Pública y Seguro Social"); public EmployeeView(final EmployeeService employeeService, @@ -174,6 +177,9 @@ public class EmployeeView extends BeanValidationForm implements HasUrl editButton.setVisible(true); reportButton.setVisible(true); birthday.addValueChangeListener(event -> calculateAge()); + birthday.setMax(java.time.LocalDate.now()); + dateOfEntry.addValueChangeListener(event -> calculateSeniority()); + dateOfEntry.addValueChangeListener(event -> calculateSeniority()); reportButton.addClickListener((ComponentEventListener>) buttonClickEvent -> { var employee = getEntity(); @@ -201,10 +207,30 @@ public class EmployeeView extends BeanValidationForm implements HasUrl int birthYear = birthday.getValue().getYear(); int ages = currentYear - birthYear; age.setValue(String.valueOf(ages)); + if (ages < 18) { + birthday.setInvalid(true); + birthday.setErrorMessage("La edad no puede ser menor a 18 años."); + Notification.show("La edad ingresada no es válida, debe ser mayor o igual a 18 años."); + } else { + birthday.setInvalid(false); + } System.out.println(age); } } + private void calculateSeniority() { + LocalDate entryDate = dateOfEntry.getValue(); + LocalDate exitDate = dateOfExit.getValue() != null ? dateOfExit.getValue() : LocalDate.now(); + + if (entryDate != null) { + long yearsOfService = ChronoUnit.YEARS.between(entryDate, exitDate); + String seniorityValue = yearsOfService + " años "; + seniority.setValue(seniorityValue); + } else { + seniority.setValue("No disponible"); + } + } + private void configureUpload() { upload.setAcceptedFileTypes("image/jpeg", "image/png"); upload.setMaxFileSize(1024 * 1024); @@ -216,11 +242,15 @@ public class EmployeeView extends BeanValidationForm implements HasUrl getEntity().setProfileImage(base64Image); - profileImagePreview.setSrc("data:image/jpeg;base64," + base64Image); + profileImagePreview.setSrc("data:image/png;base64," + base64Image); profileImagePreview.setMaxWidth("150px"); profileImagePreview.setMaxHeight("150px"); } catch (IOException e) { - Notification.show("Error al subir la imagen."); + Notification.show("Error al subir la imagen: " + e.getMessage()); + e.printStackTrace(); + } catch (Exception e) { + Notification.show("Error en el servidor al procesar la imagen."); + e.printStackTrace(); } }); } @@ -391,12 +421,12 @@ public class EmployeeView extends BeanValidationForm implements HasUrl if (employee.getProfileImage() != null && !employee.getProfileImage().isEmpty()) { profileImagePreview.setSrc("data:image/jpeg;base64," + employee.getProfileImage()); profileImagePreview.setVisible(true); - profileImagePreview.setMaxWidth("150px"); - profileImagePreview.setMaxHeight("150px"); + profileImagePreview.setMaxWidth("250px"); + profileImagePreview.setMaxHeight("250px"); - upload.setVisible(false); + upload.setVisible(true); } else { - profileImagePreview.setVisible(false); + profileImagePreview.setVisible(true); upload.setVisible(true); } } @@ -476,6 +506,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl emergencyCPhone.setReadOnly(false); emergencyCEmail.setReadOnly(false); upload.setVisible(false); + profileImagePreview.setVisible(true); age.setReadOnly(false); gender.setReadOnly(false); status.setReadOnly(false); @@ -523,7 +554,6 @@ public class EmployeeView extends BeanValidationForm implements HasUrl birthCity, residenceAddress, localAddress, maritalStatus, ci, issuedIn, numberOfChildren, phoneNumber, personalEmail, - cod, position, team, leadManager, project, contEmerg, emergencyCName, emergencyCAddress, emergencyCPhone, emergencyCEmail, infProf, titulos, pTitle1, pTitle2, pTitle3, pStudy1, pStudy2, pStudy3, @@ -531,6 +561,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl logros, recognition, achievements, idioma, language, languageLevel, infoAdm, + cod, position, team, leadManager, project, infoCont, dateOfEntry, dateOfExit, contractType, seniority, salary, datBanc, bankName, accountNumber, datGest, gpss, sss, beneficiaries, diff --git a/src/main/java/com/primefactorsolutions/views/EmployeesListView.java b/src/main/java/com/primefactorsolutions/views/EmployeesListView.java index 15fc02a..40683a4 100644 --- a/src/main/java/com/primefactorsolutions/views/EmployeesListView.java +++ b/src/main/java/com/primefactorsolutions/views/EmployeesListView.java @@ -4,7 +4,6 @@ import com.primefactorsolutions.model.Employee; import com.primefactorsolutions.service.EmployeeService; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.html.H2; -import com.vaadin.flow.component.html.Main; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.spring.annotation.SpringComponent; @@ -16,13 +15,12 @@ import org.springframework.context.annotation.Scope; import java.util.List; - @SpringComponent @Scope("prototype") @PageTitle("Employees") @Route(value = "/employees", layout = MainLayout.class) @PermitAll -public class EmployeesListView extends Main { +public class EmployeesListView extends BaseView { private final EmployeeService employeeService; private final PagingGrid table = new PagingGrid<>(Employee.class); @@ -34,10 +32,10 @@ public class EmployeesListView extends Main { } private void setupView() { - add(new H2("Employee List")); + getCurrentPageLayout().add(new H2("Employee List")); configureTable(); - add(createAddEmployeeButton()); - add(table); + getCurrentPageLayout().add(createAddEmployeeButton()); + getCurrentPageLayout().add(table); } private void configureTable() { @@ -75,7 +73,7 @@ public class EmployeesListView extends Main { private void setupPagingGrid() { table.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM); - table.setPageSize(5); + table.setPageSize(Constants.PAGE_SIZE); } private void refreshGrid() { diff --git a/src/main/java/com/primefactorsolutions/views/HoursWorkedListView.java b/src/main/java/com/primefactorsolutions/views/HoursWorkedListView.java new file mode 100644 index 0000000..6b8bf52 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/views/HoursWorkedListView.java @@ -0,0 +1,256 @@ +package com.primefactorsolutions.views; + +import com.primefactorsolutions.model.Employee; +import com.primefactorsolutions.model.HoursWorked; +import com.primefactorsolutions.model.Team; +import com.primefactorsolutions.service.EmployeeService; +import com.primefactorsolutions.service.HoursWorkedService; +import com.primefactorsolutions.service.TeamService; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import jakarta.annotation.security.PermitAll; +import org.springframework.context.annotation.Scope; +import org.vaadin.firitin.components.grid.PagingGrid; + +import java.time.LocalDate; +import java.time.temporal.IsoFields; +import java.util.*; +import java.util.stream.Collectors; + +import static com.primefactorsolutions.views.Constants.PAGE_SIZE; + +@SpringComponent +@PermitAll +@Scope("prototype") +@PageTitle("Registro de Horas Trabajadas") +@Route(value = "/hours-worked-list", layout = MainLayout.class) +public class HoursWorkedListView extends BaseView { + + private final HoursWorkedService hoursWorkedService; + private final EmployeeService employeeService; + private final TeamService teamService; + private final PagingGrid hoursWorkedGrid = new PagingGrid<>(); + private ComboBox employeeFilter; + private ComboBox teamFilter; + private UUID selectedEmployeeId; + + + public HoursWorkedListView(final HoursWorkedService hoursWorkedService, + final EmployeeService employeeService, + final TeamService teamService) { + this.hoursWorkedService = hoursWorkedService; + this.employeeService = employeeService; + this.teamService = teamService; + + initializeView(); + refreshGridListHoursWorked(null, null); + } + + private void refreshGridListHoursWorked(final Employee employee, + final Team team) { + hoursWorkedGrid.setPagingDataProvider((page, pageSize) -> { + int start = (int) (page * hoursWorkedGrid.getPageSize()); + List hoursWorkedList = fetchFilteredHoursWorked(start, pageSize, employee, team); + + double totalHours = hoursWorkedList.stream() + .mapToDouble(HoursWorked::getTotalHours) + .sum(); + + Notification.show("Total de horas trabajadas: " + totalHours, + 3000, Notification.Position.BOTTOM_CENTER); + + return hoursWorkedList; + }); + hoursWorkedGrid.getDataProvider().refreshAll(); + } + + private List fetchFilteredHoursWorked(final int start, + final int pageSize, + final Employee employee, + final Team team) { + List filteredHoursWorked = hoursWorkedService.findAll(); + + if (employee != null && !"TODOS".equals(employee.getFirstName())) { + filteredHoursWorked = filteredHoursWorked.stream() + .filter(hw -> hw.getEmployee().getId().equals(employee.getId())) + .collect(Collectors.toList()); + } + + if (team != null && !"TODOS".equals(team.getName())) { + filteredHoursWorked = filteredHoursWorked.stream() + .filter(hw -> hw.getEmployee().getTeam() != null + && hw.getEmployee().getTeam().getId().equals(team.getId())) + .collect(Collectors.toList()); + } + + for (HoursWorked hoursWorked : filteredHoursWorked) { + if (employee != null && hoursWorked.getEmployee().getId().equals(employee.getId())) { + LocalDate date = hoursWorked.getDate(); + int currentWeek = date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); + + double totalWorkedInSameWeek = filteredHoursWorked.stream() + .filter(hw -> hw.getEmployee().getId().equals(employee.getId()) + && + hw.getDate().get(IsoFields.WEEK_OF_WEEK_BASED_YEAR) == currentWeek) + .mapToDouble(HoursWorked::getHours) + .sum(); + + double updatedPendingHours = totalWorkedInSameWeek - hoursWorked.getHours(); + hoursWorked.setHoraspendientes(updatedPendingHours); + } + } + + int end = Math.min(start + pageSize, filteredHoursWorked.size()); + return filteredHoursWorked.subList(start, end); + } + + private void initializeView() { + getCurrentPageLayout().add(createAddHoursWorked()); + setupFilters(); + setupListHoursWorkedGrid(); + getCurrentPageLayout().add(hoursWorkedGrid); + getCurrentPageLayout().add(createActionButtons()); + } + + private void setupFilters() { + final HorizontalLayout hl = new HorizontalLayout(); + hl.add(createEmployeeFilter()); + hl.add(createTeamFilter()); + + getCurrentPageLayout().add(hl); + } + + private void setupListHoursWorkedGrid() { + hoursWorkedGrid.addColumn(hw -> hw.getDate() != null ? hw.getDate().toString() : "") + .setHeader("Fecha") + .setSortable(true); + hoursWorkedGrid.addColumn(HoursWorked::getWeekNumber) + .setHeader("Semana") + .setSortable(true); + hoursWorkedGrid.addColumn(hw -> hw.getEmployee().getFirstName() + " " + hw.getEmployee().getLastName()) + .setHeader("Empleado"); + hoursWorkedGrid.addColumn(hw -> hw.getEmployee().getTeam() != null ? hw.getEmployee().getTeam() + .getName() : "Sin asignar") + .setHeader("Equipo"); + hoursWorkedGrid.addColumn(HoursWorked::getActividad).setHeader("Actividad"); + hoursWorkedGrid.addColumn(HoursWorked::getHours).setHeader("Total Horas").setSortable(true); + hoursWorkedGrid.addColumn(hw -> hw.getHoraspendientes() - calcularTotal(hw)).setHeader("Horas Pendientes") + .setSortable(true); + + hoursWorkedGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM); + hoursWorkedGrid.setPageSize(PAGE_SIZE); + hoursWorkedGrid.asSingleSelect().addValueChangeListener(event -> { + HoursWorked selectedHoursWorked = event.getValue(); + if (selectedHoursWorked != null) { + selectedEmployeeId = selectedHoursWorked.getEmployee().getId(); + } + }); + } + + private double calcularTotal(final HoursWorked hoursWorked) { + List listHoursworkedemploye = hoursWorkedService.findListHoursWorkedEmployee( + hoursWorked.getEmployee().getId(), hoursWorked.getWeekNumber()); + return calculateTotalUtilized(listHoursworkedemploye); + } + + private double calculateTotalUtilized(final List employeeRequests) { + return employeeRequests.stream() + .filter(Objects::nonNull) + .mapToDouble(HoursWorked::getHours) + .sum(); + } + + private HorizontalLayout createActionButtons() { + Button viewButton = new Button("Ver", event -> { + if (selectedEmployeeId != null) { + navigateToHoursWorkedView(selectedEmployeeId); + } else { + Notification.show("Seleccione una solicitud.", 3000, Notification.Position.MIDDLE); + } + }); + Button closeButton = new Button("Salir", event -> navigateToListView()); + return new HorizontalLayout(viewButton, closeButton); + } + + private void navigateToListView() { + getUI().ifPresent(ui -> ui.navigate(MainView.class)); + } + + private void navigateToHoursWorkedView(final UUID idEmployee) { + getUI().ifPresent(ui -> ui.navigate("hours-worked-list/" + idEmployee.toString())); + } + + private Button createButton(final String label, final Runnable onClickAction) { + final Button button = new Button(label); + button.addClickListener(event -> onClickAction.run()); + + return button; + } + + private Button createAddHoursWorked() { + return createButton("Agregar Actividad", this::navigateToHours); + } + + private void navigateToHours() { + getUI().ifPresent(ui -> ui.navigate(HoursWorkedView.class, "new")); + } + + private ComboBox createEmployeeFilter() { + employeeFilter = new ComboBox<>("Empleado"); + final List employees = new ArrayList<>(employeeService.findAllEmployees()); + employees.addFirst(createAllEmployeesOption()); + employeeFilter.setItems(employees); + employeeFilter.setItemLabelGenerator(this::getEmployeeFullName); + employeeFilter.setValue(employees.getFirst()); + employeeFilter.addValueChangeListener(event -> + refreshGridListHoursWorked( + event.getValue(), + teamFilter.getValue() + ) + ); + + return employeeFilter; + } + + private String getEmployeeFullName(final Employee employee) { + return "TODOS".equals(employee.getFirstName()) + ? "TODOS" : employee.getFirstName() + " " + employee.getLastName(); + } + + private ComboBox createTeamFilter() { + teamFilter = new ComboBox<>("Equipo"); + List teams = new ArrayList<>(teamService.findAllTeams()); + teams.addFirst(createAllTeamsOption()); + teamFilter.setItems(teams); + teamFilter.setItemLabelGenerator(this::getTeamLabel); + teamFilter.setValue(teams.getFirst()); + teamFilter.addValueChangeListener(event -> + refreshGridListHoursWorked( + employeeFilter.getValue(), + event.getValue() + ) + ); + return teamFilter; + } + + private String getTeamLabel(final Team team) { + return team != null && !"TODOS".equals(team.getName()) ? team.getName() : "TODOS"; + } + + private Employee createAllEmployeesOption() { + Employee allEmployeesOption = new Employee(); + allEmployeesOption.setFirstName("TODOS"); + return allEmployeesOption; + } + + private Team createAllTeamsOption() { + Team allTeamsOption = new Team(); + allTeamsOption.setName("TODOS"); + return allTeamsOption; + } +} diff --git a/src/main/java/com/primefactorsolutions/views/HoursWorkedMonthView.java b/src/main/java/com/primefactorsolutions/views/HoursWorkedMonthView.java deleted file mode 100644 index 88d86e6..0000000 --- a/src/main/java/com/primefactorsolutions/views/HoursWorkedMonthView.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.primefactorsolutions.views; - -import com.primefactorsolutions.model.Employee; -import com.primefactorsolutions.model.Actividad; -import com.primefactorsolutions.service.EmployeeService; -import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.datepicker.DatePicker; -import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.component.html.Label; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.orderedlayout.HorizontalLayout; -import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.router.PageTitle; -import com.vaadin.flow.router.Route; -import com.vaadin.flow.spring.annotation.SpringComponent; -import jakarta.annotation.security.PermitAll; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; - -import java.time.LocalDate; -import java.util.List; - -@SpringComponent -@PermitAll -@Scope("prototype") -@PageTitle("Hours Worked Month") -@Route(value = "/hours-worked-month/me", layout = MainLayout.class) -public class HoursWorkedMonthView extends VerticalLayout { - private final EmployeeService employeeService; - private final ComboBox employeeComboBox = new ComboBox<>("Empleado"); - private final ComboBox equipoDropdown = new ComboBox<>("Equipo"); - private final Grid grid = new Grid<>(Actividad.class); - - private final Label totalCompletadoLabel = new Label(); - private final Label horasPendientesLabel = new Label(); - private final Label totalAcumuladasLabel = new Label(); - private final Label horasAdeudadasLabel = new Label(); - private final Button actualizarButton = new Button("Actualizar"); - private final Button guardarButton = new Button("Guardar"); - private final Button cerrarButton = new Button("Cerrar"); - - private LocalDate selectedMonth; - - @Autowired - public HoursWorkedMonthView(final EmployeeService employeeService) { - this.employeeService = employeeService; - configurarVista(); - } - - private void configurarVista() { - DatePicker monthPicker = new DatePicker("Selecciona un mes"); - monthPicker.setValue(LocalDate.now()); - monthPicker.addValueChangeListener(event -> { - selectedMonth = event.getValue().withDayOfMonth(1); - //cargarDatosMes(selectedMonth); - }); - - equipoDropdown.setItems("Equipo 1", "Equipo 2", "Equipo 3"); - equipoDropdown.setWidth("250px"); - - setEmployeeComboBoxProperties(); - - configurarGrid(); - - actualizarButton.addClickListener(event -> actualizarDatos()); - guardarButton.addClickListener(event -> guardarActividades()); - cerrarButton.addClickListener(event -> closeView()); - - HorizontalLayout headerLayout = new HorizontalLayout(monthPicker, equipoDropdown, employeeComboBox); - add( - headerLayout, grid, totalCompletadoLabel, - horasPendientesLabel, totalAcumuladasLabel, - horasAdeudadasLabel, actualizarButton, - guardarButton, cerrarButton); - } - - private void setEmployeeComboBoxProperties() { - employeeComboBox.setWidth("250px"); - employeeComboBox.setPlaceholder("Buscar empleado..."); - employeeComboBox.setItems(employeeService.findAllEmployees()); - employeeComboBox.setItemLabelGenerator(employee -> employee.getFirstName() + " " + employee.getLastName()); - } - - private void configurarGrid() { - grid.removeAllColumns(); - grid.addColumn(Actividad::getLunes).setHeader("Lunes"); - grid.addColumn(Actividad::getMartes).setHeader("Martes"); - grid.addColumn(Actividad::getMiercoles).setHeader("Miércoles"); - grid.addColumn(Actividad::getJueves).setHeader("Jueves"); - grid.addColumn(Actividad::getViernes).setHeader("Viernes"); - grid.addColumn(Actividad::getSabado).setHeader("Sábado"); - grid.addColumn(Actividad::getDomingo).setHeader("Domingo"); - grid.addColumn(this::calcularTotalPorDia).setHeader("Total Semanal").setKey("totalSemanal"); - } - -// private void cargarDatosMes(final LocalDate month) { -// List actividadesDelMes = obtenerActividadesDelMes(month); -// grid.setItems(actividadesDelMes); -// -// double totalCompletado = calcularTotalCompletado(actividadesDelMes); -// double horasPendientes = calcularHorasPendientes(totalCompletado); -// double totalAcumuladas = 166; -// double horasAdeudadas = 2; -// -// totalCompletadoLabel.setText("Prom. Hrs/Semana Completadas: " + totalCompletado); -// horasPendientesLabel.setText("Prom. Hrs/Semana Pendientes: " + horasPendientes); -// totalAcumuladasLabel.setText("Total Hrs./Mes Acumuladas: " + totalAcumuladas); -// horasAdeudadasLabel.setText("Total Hrs./Mes Adeudadas: " + horasAdeudadas); -// } - -// private List obtenerActividadesDelMes(final LocalDate month) { -// LocalDate startOfMonth = month.with(TemporalAdjusters.firstDayOfMonth()); -// LocalDate endOfMonth = month.with(TemporalAdjusters.lastDayOfMonth()); -// -// List actividadesDelMes = new ArrayList<>(); -// -// for (LocalDate date = startOfMonth; date.isBefore(endOfMonth.plusDays(1)); date = date.plusDays(1)) { -// Actividad actividad = new Actividad.Builder() -// .lunes(0) -// .martes(0) -// .miercoles(0) -// .jueves(0) -// .viernes(0) -// .sabado(0) -// .domingo(0) -// .build(); -// actividadesDelMes.add(actividad); -// } -// -// return actividadesDelMes; -// } - - private double calcularTotalCompletado(final List actividades) { - return actividades.stream() - .mapToDouble(this::calcularTotalPorDia) - .sum(); - } - - private double calcularTotalPorDia(final Actividad actividad) { - return actividad.getLunes() + actividad.getMartes() + actividad.getMiercoles() - + actividad.getJueves() + actividad.getViernes() + actividad.getSabado() - + actividad.getDomingo(); - } - - private double calcularHorasPendientes(final double totalCompletado) { - return 40 - totalCompletado; - } - - private void actualizarDatos() { - Notification.show("Datos actualizados."); - } - - private void guardarActividades() { - Notification.show("Actividades guardadas correctamente."); - } - - private void closeView() { - getUI().ifPresent(ui -> ui.navigate("")); - } -} diff --git a/src/main/java/com/primefactorsolutions/views/HoursWorkedView.java b/src/main/java/com/primefactorsolutions/views/HoursWorkedView.java index 2c95054..f870414 100644 --- a/src/main/java/com/primefactorsolutions/views/HoursWorkedView.java +++ b/src/main/java/com/primefactorsolutions/views/HoursWorkedView.java @@ -1,270 +1,256 @@ package com.primefactorsolutions.views; -import com.primefactorsolutions.model.Actividad; + import com.primefactorsolutions.model.Employee; import com.primefactorsolutions.model.HoursWorked; +import com.primefactorsolutions.model.Team; import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.HoursWorkedService; -import com.vaadin.flow.component.datepicker.DatePicker; -import com.vaadin.flow.component.grid.Grid; +import com.primefactorsolutions.service.TeamService; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Label; import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.router.PageTitle; -import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.*; import com.vaadin.flow.spring.annotation.SpringComponent; import jakarta.annotation.security.PermitAll; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; -import com.vaadin.flow.component.html.Label; +import org.vaadin.firitin.components.datepicker.VDatePicker; +import org.vaadin.firitin.form.BeanValidationForm; - -import java.time.DayOfWeek; import java.time.LocalDate; +import java.time.YearMonth; import java.time.temporal.IsoFields; -import java.time.temporal.WeekFields; import java.util.ArrayList; import java.util.List; -import java.util.Locale; +import java.util.UUID; @SpringComponent @PermitAll @Scope("prototype") -@PageTitle("Hours Worked") -@Route(value = "/hours-worked/me", layout = MainLayout.class) -public class HoursWorkedView extends VerticalLayout { - private final List actividades = new ArrayList<>(); - private final Grid grid = new Grid<>(Actividad.class); +@PageTitle("Horas Trabajadas") +@Route(value = "/hours-worked-list/:hours-workedId?/:action?", layout = MainLayout.class) +public class HoursWorkedView extends BeanValidationForm implements HasUrlParameter { + private final VDatePicker dateField = new VDatePicker("Fecha"); + private final ComboBox teamField = new ComboBox<>("Equipo"); + private ComboBox employeeField; + private final ComboBox tareasEspecificasDropdown = new ComboBox<>("Tarea Específica"); + private final TextField tareaEspecificaInput = new TextField("Otra Tarea Específica"); + private final TextField horasTareaEspecificaField = new TextField("Horas Tarea Específica"); + private final TextField activityField = new TextField("Actividad"); + private final TextField hoursField = new TextField("Horas"); - private final ComboBox employeeComboBox = new ComboBox<>("Employee"); - private LocalDate selectedStartOfWeek; - private int weekNumber; - - @Autowired - private final EmployeeService employeeService; - - private final Label fechasLabel = new Label("Selecciona una semana para ver las fechas."); + private final H2 equipoLabel = new H2("Tareas del Cliente/Equipo"); + private final H2 empresaLabel = new H2("Tareas de la Empresa"); private final Label totalCompletadoLabel = new Label(); - private final Label horasPendientesLabel = new Label(); - @Autowired private final HoursWorkedService hoursWorkedService; - public HoursWorkedView(final EmployeeService employeeService, final HoursWorkedService hoursWorkedService) { - this.employeeService = employeeService; + private final EmployeeService employeeService; + private final TeamService teamService; + private HoursWorked hoursWorked; + private Employee employee; + + private Button saveButton; + + public HoursWorkedView(final HoursWorkedService hoursWorkedService, + final EmployeeService employeeService, + final TeamService teamService) { + super(HoursWorked.class); this.hoursWorkedService = hoursWorkedService; - configurarVista(); - cargarDatos(); + this.employeeService = employeeService; + this.teamService = teamService; + + initializeDateField(); + initializeTeamField(); + initializeEmployeeField(); + configureTareasEspecificas(); + } - private void cargarDatos() { - List listaDeHorasTrabajadas = obtenerDatos(); // Obtenemos la lista aquí - grid.setItems(actividades); + @Override + public void setParameter(final BeforeEvent beforeEvent, final String action) { + final RouteParameters params = beforeEvent.getRouteParameters(); + final String s = params.get("hours-workedId").orElse(null); - double totalHoras = calcularTotalHoras(listaDeHorasTrabajadas); // Pasa la lista aquí + if ("new".equals(action)) { + setEntityWithEnabledSave(new HoursWorked()); + } else { + UUID hoursWorkedId = UUID.fromString(s); + var hoursWorked = hoursWorkedService.getHoursWorked(hoursWorkedId); + setEntityWithEnabledSave(hoursWorked); + + if ("edit".equals(action) && !s.isEmpty()) { + saveButton.setVisible(true); + } else if ("view".equals(action) && !s.isEmpty()) { + saveButton.setVisible(false); + } + } } - private void setEmployeeComboBoxProperties() { - employeeComboBox.setWidth("250px"); - employeeComboBox.setPlaceholder("Buscar empleado..."); - employeeComboBox.setItems(employeeService.findAllEmployees()); - employeeComboBox.setItemLabelGenerator(employee -> employee.getFirstName() + " " + employee.getLastName()); - - employeeComboBox.setAllowCustomValue(false); - employeeComboBox.addCustomValueSetListener(event -> - Notification.show("Selecciona un empleado válido de la lista.") + @Override + protected List getFormComponents() { + return List.of( + dateField, + teamField, + employeeField, + equipoLabel, + activityField, + hoursField, + empresaLabel, + tareasEspecificasDropdown, + tareaEspecificaInput, + horasTareaEspecificaField, + createCloseButton() ); + } - employeeComboBox.addValueChangeListener(event -> { - Employee selectedEmployee = event.getValue(); - if (selectedEmployee != null) { - Notification.show("Empleado seleccionado: " - + selectedEmployee.getFirstName() + " " - + selectedEmployee.getLastName()); + private void configureTareasEspecificas() { + tareasEspecificasDropdown.setItems("Entrevistas", "Reuniones", + "Colaboraciones", "Aprendizajes", "Proyectos PFS", "Otros"); + tareasEspecificasDropdown.setPlaceholder("Selecciona una tarea..."); + + tareasEspecificasDropdown.addValueChangeListener(event -> { + String selected = event.getValue(); + boolean isOtros = "Otros".equals(selected); + tareaEspecificaInput.setVisible(isOtros); + horasTareaEspecificaField.setVisible(true); + if (!isOtros) { + tareaEspecificaInput.clear(); + horasTareaEspecificaField.clear(); } }); + tareaEspecificaInput.setVisible(false); + horasTareaEspecificaField.setVisible(false); } - private int getWeekOfYear(final LocalDate date) { - return date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); + protected Button createSaveButton() { + saveButton = new Button("Guardar"); + saveButton.addClickListener(event -> saveHoursWorked()); + return saveButton; } - private void configurarVista() { - DatePicker fechaPicker = new DatePicker("Selecciona una fecha"); - fechaPicker.addValueChangeListener(event -> { + protected Button createCloseButton() { + Button closeButton = new Button("Cerrar"); + closeButton.addClickListener(event -> closeForm()); + return closeButton; + } + + private void initializeTeamField() { + List teams = new ArrayList<>(teamService.findAllTeams()); + teamField.setItems(teamService.findAllTeams()); + teamField.setItemLabelGenerator(Team::getName); + teamField.setValue(teams.getFirst()); + teamField.addValueChangeListener(event -> { + if (teams != null) { + employeeField.getValue(); + event.getValue(); + } + } + ); + } + + private ComboBox initializeEmployeeField() { + employeeField = new ComboBox<>("Empleado"); + List employees = new ArrayList<>(employeeService.findAllEmployees()); + employeeField.setItems(employees); + employeeField.setItemLabelGenerator(this::getEmployeeFullName); + employeeField.setValue(employees.getFirst()); + return employeeField; + } + + private String getEmployeeFullName(final Employee employee) { + return "TODOS".equals(employee.getFirstName()) + ? "TODOS" : employee.getFirstName() + " " + employee.getLastName(); + } + + private void initializeDateField() { + LocalDate today = LocalDate.now(); + YearMonth currentMonth = YearMonth.of(today.getYear(), today.getMonth()); + + LocalDate startOfMonth = currentMonth.atDay(1); + + LocalDate maxSelectableDate = today; + + dateField.setMin(startOfMonth); + dateField.setMax(maxSelectableDate); + dateField.setValue(today); + + dateField.addValueChangeListener(event -> { LocalDate selectedDate = event.getValue(); if (selectedDate != null) { - selectedStartOfWeek = getStartOfWeek(selectedDate); - LocalDate endOfWeek = selectedStartOfWeek.plusDays(6); - fechasLabel.setText("Semana del " + selectedStartOfWeek + " al " + endOfWeek); - weekNumber = getWeekOfYear(selectedDate); + int weekNumber = selectedDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); + Notification.show("Número de la semana: " + weekNumber, 3000, Notification.Position.BOTTOM_CENTER); + if (hoursWorked != null) { + hoursWorked.setWeekNumber(weekNumber); + } } }); - - Button verMesButton = new Button("Ver Mes", event -> { - getUI().ifPresent(ui -> ui.navigate(HoursWorkedMonthView.class)); - }); - - ComboBox equipoDropdown = new ComboBox<>("Equipo"); - equipoDropdown.setItems("Equipo 1", "Equipo 2", "Equipo 3"); - equipoDropdown.setWidth("250px"); - - setEmployeeComboBoxProperties(); - - HorizontalLayout filtersLayout = new HorizontalLayout(equipoDropdown, employeeComboBox); - filtersLayout.setSpacing(true); - - configurarGrid(); - HorizontalLayout actividadFormLayout = configurarFormularioActividades(); - - Button actualizarButton = new Button("Actualizar Totales", event -> actualizarTotales()); - Button guardarButton = new Button("Guardar", event -> guardarActividades()); - Button cerrarButton = new Button("Cerrar", event -> this.closeView()); - - HorizontalLayout buttonsLayout = new HorizontalLayout(actualizarButton, guardarButton, - cerrarButton, verMesButton); - - VerticalLayout totalesLayout = new VerticalLayout(totalCompletadoLabel, horasPendientesLabel); - totalesLayout.setSpacing(true); - totalesLayout.setPadding(true); - - add(fechaPicker, fechasLabel, filtersLayout, grid, actividadFormLayout, buttonsLayout, totalesLayout); - } - private void configurarGrid() { - grid.removeAllColumns(); - grid.setItems(actividades); - grid.addColumn(Actividad::getNombre).setHeader("Actividad"); - grid.addColumn(Actividad::getLunes).setHeader("Lunes"); - grid.addColumn(Actividad::getMartes).setHeader("Martes"); - grid.addColumn(Actividad::getMiercoles).setHeader("Miércoles"); - grid.addColumn(Actividad::getJueves).setHeader("Jueves"); - grid.addColumn(Actividad::getViernes).setHeader("Viernes"); - grid.addColumn(Actividad::getSabado).setHeader("Sábado"); - grid.addColumn(Actividad::getDomingo).setHeader("Domingo"); - grid.addColumn(this::calcularTotalPorDia).setHeader("Total Día").setKey("totalDia"); - } - - private HorizontalLayout configurarFormularioActividades() { - TextField actividadNombre = new TextField("Actividad"); - actividadNombre.setWidth("200px"); - - TextField lunesHoras = crearCampoHora("Lunes"); - TextField martesHoras = crearCampoHora("Martes"); - TextField miercolesHoras = crearCampoHora("Miércoles"); - TextField juevesHoras = crearCampoHora("Jueves"); - TextField viernesHoras = crearCampoHora("Viernes"); - TextField sabadoHoras = crearCampoHora("Sábado"); - TextField domingoHoras = crearCampoHora("Domingo"); - - Button agregarActividadButton = new Button("Agregar Actividad", e -> { - try { - Actividad nuevaActividad = new Actividad.Builder() - .nombre(actividadNombre.getValue()) - .lunes(parseHoras(lunesHoras.getValue())) - .martes(parseHoras(martesHoras.getValue())) - .miercoles(parseHoras(miercolesHoras.getValue())) - .jueves(parseHoras(juevesHoras.getValue())) - .viernes(parseHoras(viernesHoras.getValue())) - .sabado(parseHoras(sabadoHoras.getValue())) - .domingo(parseHoras(domingoHoras.getValue())) - .build(); - - actividades.add(nuevaActividad); - grid.setItems(actividades); - actualizarTotales(); - Notification.show("Actividad agregada correctamente"); - // Limpiar los campos de entrada - actividadNombre.clear(); - lunesHoras.clear(); - martesHoras.clear(); - miercolesHoras.clear(); - juevesHoras.clear(); - viernesHoras.clear(); - sabadoHoras.clear(); - domingoHoras.clear(); - } catch (NumberFormatException ex) { - Notification.show("Error: Por favor ingresa números válidos para las horas."); - } - }); - - return new HorizontalLayout( - actividadNombre, lunesHoras, martesHoras, miercolesHoras, - juevesHoras, viernesHoras, sabadoHoras, domingoHoras, agregarActividadButton); - } - - private TextField crearCampoHora(final String placeholder) { - TextField field = new TextField(placeholder); - field.setWidth("80px"); - field.setPlaceholder("0.0"); - return field; - } - - private double parseHoras(final String value) { - if (value == null || value.trim().isEmpty()) { - return 0.0; + private void saveHoursWorked() { + if (isFormValid()) { + HoursWorked hoursWorked = getEntity(); + setFieldValues(hoursWorked); + hoursWorkedService.save(hoursWorked); + Notification.show("Horas trabajadas guardadas correctamente."); + closeForm(); } - return Double.parseDouble(value); } - private LocalDate getStartOfWeek(final LocalDate date) { - WeekFields weekFields = WeekFields.of(Locale.getDefault()); - return date.with(weekFields.dayOfWeek(), DayOfWeek.MONDAY.getValue()); - } - - private double calcularTotalPorDia(final Actividad actividad) { - return actividad.getLunes() + actividad.getMartes() + actividad.getMiercoles() - + actividad.getJueves() + actividad.getViernes() + actividad.getSabado() + actividad.getDomingo(); - } - - private void actualizarTotales() { - double totalSemanaCompletada = actividades.stream() - .mapToDouble(this::calcularTotalPorDia) - .sum(); - double horasPendientes = 40 - totalSemanaCompletada; - - totalCompletadoLabel.setText("Total Hrs/Semana Completadas: " + totalSemanaCompletada); - horasPendientesLabel.setText("Horas Pendientes: " + horasPendientes); - } - - private void guardarActividades() { - Employee selectedEmployee = employeeComboBox.getValue(); - - if (selectedEmployee == null) { - Notification.show("Por favor, selecciona un empleado antes de guardar."); - return; - } - - double totalHorasSemana = actividades.stream() - .mapToDouble(this::calcularTotalPorDia) - .sum(); - - HoursWorked hoursWorked = new HoursWorked(); - hoursWorked.setEmployee(selectedEmployee); - hoursWorked.setWeekNumber(weekNumber); - hoursWorked.setTotalHours(totalHorasSemana); - + private void setFieldValues(final HoursWorked hoursWorked) { + hoursWorked.setDate(dateField.getValue()); + hoursWorked.setTeam(teamField.getValue()); + hoursWorked.setEmployee(employeeField.getValue()); + hoursWorked.setActividad(activityField.getValue()); try { - hoursWorkedService.saveHoursWorked(hoursWorked); // Usa saveHoursWorked directamente - Notification.show("Actividades guardadas correctamente."); - } catch (Exception e) { - Notification.show("Error al guardar actividades: " + e.getMessage()); + double hours = Double.parseDouble(hoursField.getValue()); + hoursWorked.setHours(hours); + } catch (NumberFormatException e) { + Notification.show("Por favor, ingrese un número válido para las horas."); + } + if ("Otros".equals(tareasEspecificasDropdown.getValue())) { + hoursWorked.setTareasEspecificas(tareaEspecificaInput.getValue()); + try { + double horasEspecifica = Double.parseDouble(horasTareaEspecificaField.getValue()); + hoursWorked.setHorasTareasEspecificas(horasEspecifica); + double totalHoras = hoursWorked.getHours() + horasEspecifica; + hoursWorked.setTotalHours(totalHoras); + } catch (NumberFormatException e) { + Notification.show("Por favor, ingrese un número válido para las horas de la tarea específica."); + } } } - private double calcularTotalHoras(final List listaDeHorasTrabajadas) { - return listaDeHorasTrabajadas.stream() - .mapToDouble(HoursWorked::getTotalHours) - .sum(); + private void closeForm() { + getUI().ifPresent(ui -> ui.navigate("hours-worked-list/" + hoursWorked.getId().toString())); } - private List obtenerDatos() { - return new ArrayList<>(); + private boolean isFormValid() { + return dateField.getValue() != null + && + teamField.getValue() != null + && + employeeField.getValue() != null + && + !activityField.isEmpty(); + } - private void closeView() { - getUI().ifPresent(ui -> ui.navigate(HoursWorkedView.class)); + private void configureViewOrEditAction(final String action) { + if ("edit".equals(action) && hoursWorked != null) { + setFieldsReadOnly(false); + } else if ("view".equals(action) && hoursWorked != null) { + setFieldsReadOnly(true); + saveButton.setEnabled(false); + } } -} \ No newline at end of file + + private void setFieldsReadOnly(final boolean readOnly) { + dateField.setReadOnly(readOnly); + teamField.setReadOnly(readOnly); + employeeField.setReadOnly(readOnly); + activityField.setReadOnly(readOnly); + } +} diff --git a/src/main/java/com/primefactorsolutions/views/InitAccountView.java b/src/main/java/com/primefactorsolutions/views/InitAccountView.java index 2cc8524..a3ce15d 100644 --- a/src/main/java/com/primefactorsolutions/views/InitAccountView.java +++ b/src/main/java/com/primefactorsolutions/views/InitAccountView.java @@ -13,6 +13,7 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.auth.AnonymousAllowed; +@SuppressWarnings("unused") @Route("init-account") @PageTitle("PFS Intra") @AnonymousAllowed diff --git a/src/main/java/com/primefactorsolutions/views/LoginView.java b/src/main/java/com/primefactorsolutions/views/LoginView.java index 9a280c5..3b7dd9a 100644 --- a/src/main/java/com/primefactorsolutions/views/LoginView.java +++ b/src/main/java/com/primefactorsolutions/views/LoginView.java @@ -2,6 +2,7 @@ package com.primefactorsolutions.views; import com.vaadin.flow.component.html.Anchor; import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.login.LoginForm; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.BeforeEnterEvent; @@ -9,6 +10,9 @@ import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.auth.AnonymousAllowed; +import com.vaadin.flow.theme.lumo.LumoUtility; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; @Route("login") @PageTitle("PFS Intra") @@ -17,7 +21,7 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver { private final LoginForm login = new LoginForm(); - public LoginView() { + public LoginView(@Autowired @Value("${git.commit.id.abbrev}") final String commitId) { addClassName("login-view"); setSizeFull(); setAlignItems(Alignment.CENTER); @@ -29,6 +33,10 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver { add(new H1("PFS Intra")); add(login); add(new Anchor("/password-recovery", "Reset password?")); + + final Span version = new Span(String.format("v.%s", commitId)); + version.addClassName(LumoUtility.FontSize.XSMALL); + add(version); } @Override diff --git a/src/main/java/com/primefactorsolutions/views/MainLayout.java b/src/main/java/com/primefactorsolutions/views/MainLayout.java index 5670c38..8b23940 100644 --- a/src/main/java/com/primefactorsolutions/views/MainLayout.java +++ b/src/main/java/com/primefactorsolutions/views/MainLayout.java @@ -22,6 +22,8 @@ import com.vaadin.flow.component.sidenav.SideNavItem; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.spring.security.AuthenticationContext; import com.vaadin.flow.theme.lumo.LumoUtility; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.vaadin.lineawesome.LineAwesomeIcon; @@ -35,10 +37,11 @@ public class MainLayout extends AppLayout { private H1 viewTitle; - public MainLayout(final AuthenticationContext authContext) { + public MainLayout(final AuthenticationContext authContext, + @Autowired @Value("${git.commit.id.abbrev}") final String commitId) { this.authContext = authContext; setPrimarySection(Section.DRAWER); - addDrawerContent(); + addDrawerContent(commitId); addHeaderContent(); } @@ -112,12 +115,12 @@ public class MainLayout extends AppLayout { return icon; } - private void addDrawerContent() { + private void addDrawerContent(final String commitId) { final Span appName = new Span("pfs-intra"); appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE); final Header header = new Header(appName); final Scroller scroller = new Scroller(createNavigation()); - addToDrawer(header, scroller, createFooter()); + addToDrawer(header, scroller, createFooter(commitId)); } private SideNav createNavigation() { @@ -150,7 +153,9 @@ public class MainLayout extends AppLayout { LineAwesomeIcon.LIST_ALT.create())); SideNavItem timesheet = new SideNavItem("My Timesheet", TimesheetView.class, LineAwesomeIcon.HOURGLASS_START_SOLID.create()); - timesheet.addItem(new SideNavItem("Hours Worked", HoursWorkedView.class, + timesheet.addItem(new SideNavItem("Registro de Horas Trabajadas", HoursWorkedListView.class, + LineAwesomeIcon.ID_CARD_SOLID.create())); + timesheet.addItem(new SideNavItem("Reporte Horas Trabajadas", ReporteView.class, LineAwesomeIcon.ID_CARD_SOLID.create())); SideNavItem profile = new SideNavItem("My Profile", ProfileView.class, @@ -167,8 +172,8 @@ public class MainLayout extends AppLayout { return nav; } - private Footer createFooter() { - return new Footer(); + private Footer createFooter(final String commitId) { + return new Footer(new Text(String.format("v.%s", commitId))); } @Override diff --git a/src/main/java/com/primefactorsolutions/views/MainView.java b/src/main/java/com/primefactorsolutions/views/MainView.java index 7dc4aba..1a55317 100644 --- a/src/main/java/com/primefactorsolutions/views/MainView.java +++ b/src/main/java/com/primefactorsolutions/views/MainView.java @@ -1,6 +1,5 @@ package com.primefactorsolutions.views; -import com.primefactorsolutions.service.TimeOffRequestService; import com.vaadin.flow.component.Text; import com.vaadin.flow.component.html.Main; import com.vaadin.flow.router.PageTitle; @@ -12,7 +11,7 @@ import jakarta.annotation.security.PermitAll; @PermitAll public class MainView extends Main { - public MainView(final TimeOffRequestService requestService) { + public MainView() { add(new Text("Welcome")); } } diff --git a/src/main/java/com/primefactorsolutions/views/PendingRequestsListView.java b/src/main/java/com/primefactorsolutions/views/PendingRequestsListView.java index db9b783..7163e18 100644 --- a/src/main/java/com/primefactorsolutions/views/PendingRequestsListView.java +++ b/src/main/java/com/primefactorsolutions/views/PendingRequestsListView.java @@ -6,7 +6,6 @@ import com.primefactorsolutions.service.TeamService; import com.primefactorsolutions.service.TimeOffRequestService; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.html.Main; import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.router.PageTitle; @@ -19,32 +18,31 @@ import org.vaadin.firitin.components.grid.PagingGrid; import java.util.*; import java.util.stream.Collectors; +import static com.primefactorsolutions.views.Constants.PAGE_SIZE; + @SpringComponent @Scope("prototype") @PageTitle("PendingRequests") @Route(value = "/pending-requests", layout = MainLayout.class) @PermitAll -public class PendingRequestsListView extends Main { +public class PendingRequestsListView extends BaseView { private final TimeOffRequestService requestService; private final EmployeeService employeeService; private final TeamService teamService; private final PagingGrid pendingRequestsGrid = new PagingGrid<>(); - private List employees = Collections.emptyList(); private ComboBox employeeFilter; private ComboBox teamFilter; private ComboBox categoryFilter; private UUID selectedRequestId; - public PendingRequestsListView(final TimeOffRequestService requestService, final EmployeeService employeeService, final TeamService teamService) { this.requestService = requestService; this.employeeService = employeeService; this.teamService = teamService; - this.employees = employeeService.findAllEmployees(); initializeView(); refreshGeneralPendingRequestsGrid(null, null, null); } @@ -52,14 +50,16 @@ public class PendingRequestsListView extends Main { private void initializeView() { setupFilters(); setupPendingRequestsGrid(); - add(pendingRequestsGrid); - add(createActionButtons()); + createActionButtons(); } private void setupFilters() { - add(createEmployeeFilter()); - add(createTeamFilter()); - add(createCategoryFilter()); + final HorizontalLayout hl = new HorizontalLayout(); + hl.add(createEmployeeFilter()); + hl.add(createTeamFilter()); + hl.add(createCategoryFilter()); + + getCurrentPageLayout().add(hl); } private void setupPendingRequestsGrid() { @@ -68,20 +68,23 @@ public class PendingRequestsListView extends Main { pendingRequestsGrid.addColumn(this::getCategory).setHeader("Categoría"); pendingRequestsGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM); - pendingRequestsGrid.setPageSize(5); + pendingRequestsGrid.setPageSize(PAGE_SIZE); pendingRequestsGrid.asSingleSelect().addValueChangeListener(event -> { TimeOffRequest selectedRequest = event.getValue(); if (selectedRequest != null) { selectedRequestId = selectedRequest.getId(); } }); + + getCurrentPageLayout().add(pendingRequestsGrid); } - private HorizontalLayout createActionButtons() { - Button approveButton = createActionButton("Aprobar", TimeOffRequestStatus.APROBADO); - Button rejectButton = createActionButton("Rechazar", TimeOffRequestStatus.RECHAZADO); - Button closeButton = new Button("Salir", event -> navigateToMainView()); - return new HorizontalLayout(approveButton, rejectButton, closeButton); + private void createActionButtons() { + final Button approveButton = createActionButton("Aprobar", TimeOffRequestStatus.APROBADO); + final Button rejectButton = createActionButton("Rechazar", TimeOffRequestStatus.RECHAZADO); + final Button closeButton = new Button("Salir", event -> navigateToMainView()); + + getCurrentPageLayout().add(new HorizontalLayout(approveButton, rejectButton, closeButton)); } private Button createActionButton(final String caption, final TimeOffRequestStatus status) { diff --git a/src/main/java/com/primefactorsolutions/views/QuestionsListView.java b/src/main/java/com/primefactorsolutions/views/QuestionsListView.java index 23deca7..ffd0c15 100644 --- a/src/main/java/com/primefactorsolutions/views/QuestionsListView.java +++ b/src/main/java/com/primefactorsolutions/views/QuestionsListView.java @@ -28,16 +28,13 @@ import java.util.stream.Stream; @Route(value = "/questions", layout = MainLayout.class) @PermitAll public class QuestionsListView extends Main { - private final QuestionService questionService; public QuestionsListView(final QuestionService questionService) { - this.questionService = questionService; final HorizontalLayout hl = new HorizontalLayout(); final Button addQuestion = new Button("Add Question"); - addQuestion.addClickListener((ComponentEventListener>) buttonClickEvent -> { - this.getUI().get().navigate(QuestionView.class, "new"); - }); + addQuestion.addClickListener((ComponentEventListener>) buttonClickEvent -> + this.getUI().flatMap(ui -> ui.navigate(QuestionView.class, "new"))); hl.add(addQuestion); final VGrid grid = new VGrid<>(Question.class); @@ -45,7 +42,7 @@ public class QuestionsListView extends Main { grid.addComponentColumn((ValueProvider) question -> { final Button edit = new Button("Edit"); edit.addClickListener((ComponentEventListener>) buttonClickEvent -> - this.getUI().get().navigate(QuestionView.class, question.getId().toString())); + this.getUI().flatMap(ui -> ui.navigate(QuestionView.class, question.getId().toString()))); return edit; }); grid.setDataProvider(new DataProvider<>() { @@ -59,6 +56,7 @@ public class QuestionsListView extends Main { return questionService.getQuestions().size(); } + @SuppressWarnings("unused") @Override public Stream fetch(final Query query) { int limit = query.getLimit(); diff --git a/src/main/java/com/primefactorsolutions/views/ReporteView.java b/src/main/java/com/primefactorsolutions/views/ReporteView.java index 1416a56..2922de9 100644 --- a/src/main/java/com/primefactorsolutions/views/ReporteView.java +++ b/src/main/java/com/primefactorsolutions/views/ReporteView.java @@ -1,22 +1,34 @@ package com.primefactorsolutions.views; import com.primefactorsolutions.model.HoursWorked; +import com.primefactorsolutions.model.Team; import com.primefactorsolutions.service.HoursWorkedService; import com.primefactorsolutions.service.ReportService; +import com.primefactorsolutions.service.TeamService; import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.html.Anchor; import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.StreamResource; import jakarta.annotation.security.PermitAll; import org.springframework.beans.factory.annotation.Autowired; -import com.vaadin.flow.component.notification.Notification; +import com.primefactorsolutions.service.EmployeeService; import java.io.ByteArrayInputStream; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.format.TextStyle; +import java.time.temporal.WeekFields; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; @@ -25,63 +37,198 @@ import java.util.stream.Collectors; @PageTitle("Reporte de Horas Trabajadas") public class ReporteView extends VerticalLayout { + private final EmployeeService employeeService; private final HoursWorkedService hoursWorkedService; private final ReportService reportService; + private final TeamService teamService; + + private final ComboBox equipoComboBox = new ComboBox<>("Seleccionar Equipo"); + private final ComboBox semanaComboBox = new ComboBox<>("Seleccionar Semana"); + private final Grid> grid = new Grid<>(); + private final VerticalLayout headerLayout = new VerticalLayout(); + private Anchor downloadLink; + + private final Span semanaInfoSpan = new Span(); + + // Obtener el año actual + private int currentYear = LocalDate.now().getYear(); @Autowired - public ReporteView(final HoursWorkedService hoursWorkedService, final ReportService reportService) { + public ReporteView(final HoursWorkedService hoursWorkedService, + final ReportService reportService, final TeamService teamService, + final EmployeeService employeeService) { this.hoursWorkedService = hoursWorkedService; this.reportService = reportService; + this.teamService = teamService; + this.employeeService = employeeService; H2 title = new H2("Reporte de Horas Trabajadas"); add(title); - Button reportButton = new Button("Generar Reporte de Horas Trabajadas", event -> generateHoursWorkedReport()); - add(reportButton); + List teams = teamService.findAllTeams(); + equipoComboBox.setItems(teams); + equipoComboBox.setItemLabelGenerator(Team::getName); + + // Configurar el ComboBox de semanas + initializeSemanaComboBox(); + + // Listener para actualizar `semanaInfoSpan` con la selección del usuario en `semanaComboBox` + semanaComboBox.addValueChangeListener(event -> { + String selectedWeek = event.getValue(); + semanaInfoSpan.setText(selectedWeek != null ? selectedWeek : "Selecciona una semana"); + }); + + Button reportButton = new Button("Generar Reporte de Horas Trabajadas", + event -> generateHoursWorkedReport()); + HorizontalLayout filtersLayout = new HorizontalLayout(equipoComboBox, semanaComboBox, reportButton); + add(filtersLayout); + + // Añadir `headerLayout` al diseño principal para el encabezado dinámico + add(headerLayout); + updateHeaderLayout(null, null); + + grid.addColumn(map -> map.get("Empleado")).setHeader("Empleado"); + grid.addColumn(map -> map.get("Horas Trabajadas")).setHeader("Horas Trabajadas"); + grid.addColumn(map -> map.get("Horas Pendientes")).setHeader("Horas Pendientes"); + grid.addColumn(map -> map.get("Observaciones")).setHeader("Observaciones"); + + add(grid); + } + + private void initializeSemanaComboBox() { + int year = LocalDate.now().getYear(); + LocalDate startOfYear = LocalDate.of(year, 1, 5); // Suponemos que la semana comienza el 5 de enero. + + List semanas = startOfYear.datesUntil(LocalDate.of(year + 1, 1, 1), + java.time.Period.ofWeeks(1)) + .map(date -> { + int weekNumber = date.get(WeekFields.of(DayOfWeek.MONDAY, 1) + .weekOfWeekBasedYear()); + LocalDate startOfWeek = date; + LocalDate endOfWeek = startOfWeek.plusDays(6); + + return String.format("Semana %d: %s - %s", + weekNumber, + startOfWeek.getDayOfMonth() + " de " + startOfWeek.getMonth() + .getDisplayName(TextStyle.FULL, Locale.getDefault()), + endOfWeek.getDayOfMonth() + " de " + endOfWeek.getMonth() + .getDisplayName(TextStyle.FULL, Locale.getDefault()) + ); + }) + .collect(Collectors.toList()); + + semanaComboBox.setItems(semanas); + semanaComboBox.setPlaceholder("Seleccione una semana"); } private void generateHoursWorkedReport() { - List hoursWorkedList = hoursWorkedService.findAll(); // Obtener la lista de HoursWorked + Team selectedEquipo = equipoComboBox.getValue(); + String selectedWeek = semanaComboBox.getValue(); + if (selectedEquipo == null || selectedWeek == null) { + Notification.show("Por favor, selecciona un equipo y una semana para generar el reporte.", + 3000, Notification.Position.MIDDLE); + return; + } + int weekNumber = Integer.parseInt(selectedWeek.split(" ")[1].replace(":", "")); + LocalDate selectedDate = LocalDate.now().with(WeekFields.of(DayOfWeek.FRIDAY, 1) + .weekOfWeekBasedYear(), weekNumber); + updateHeaderLayout(selectedEquipo, selectedDate); + + List hoursWorkedList = hoursWorkedService.findAll().stream() + .filter(hw -> hw.getEmployee().getTeam().getId().equals(selectedEquipo + .getId()) && hw.getWeekNumber() == weekNumber) + .collect(Collectors.toList()); + + System.out.println(hoursWorkedList); if (hoursWorkedList.isEmpty()) { + Notification.show("No hay horas trabajadas disponibles para generar el reporte.", 3000, Notification.Position.MIDDLE); return; } - try { - List headers = List.of("ID", "Employee ID", "Week Number", "Total Hours"); + List> data = hoursWorkedList.stream() + .map(hoursWorked -> { + Map map = new HashMap<>(); + map.put("ID", hoursWorked.getId().toString()); + map.put("Employee ID", hoursWorked.getEmployee().getId().toString()); + map.put("Empleado", hoursWorked.getEmployee().getFirstName() + " " + + hoursWorked.getEmployee().getLastName()); + map.put("Horas Trabajadas", hoursWorked.getTotalHours()); + map.put("Horas Pendientes", 40 - hoursWorked.getTotalHours()); + map.put("Observaciones", ""); + return map; + }) + .collect(Collectors.toList()); - List> data = hoursWorkedList.stream() - .map(hoursWorked -> { - Map map = new HashMap<>(); - map.put("ID", hoursWorked.getId().toString()); - map.put("Employee ID", hoursWorked.getEmployee().getId().toString()); - map.put("Week Number", hoursWorked.getWeekNumber()); - map.put("Total Hours", hoursWorked.getTotalHours()); - return map; - }) - .collect(Collectors.toList()); + grid.setItems(data); + generateExcelDownloadLink(data, weekNumber); + } - byte[] excelBytes = reportService.writeAsExcel("hours_worked_report", headers, data); + private void updateHeaderLayout(final Team team, final LocalDate dateInWeek) { + headerLayout.removeAll(); - StreamResource excelResource = new StreamResource("hours_worked_report.xlsx", - () -> new ByteArrayInputStream(excelBytes)); - excelResource.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - excelResource.setCacheTime(0); + if (team != null && dateInWeek != null) { + LocalDate startOfWeek = dateInWeek.with(DayOfWeek.FRIDAY); + LocalDate endOfWeek = dateInWeek.with(DayOfWeek.THURSDAY); + int weekNumber = getWeekOfYear(dateInWeek); - Anchor downloadLink = new Anchor(excelResource, "Descargar Reporte de Horas Trabajadas"); - downloadLink.getElement().setAttribute("download", true); + String formattedStartDate = startOfWeek.getDayOfMonth() + " de " + + startOfWeek.getMonth().getDisplayName(TextStyle.FULL, Locale.getDefault()); + String formattedEndDate = endOfWeek.getDayOfMonth() + " de " + + endOfWeek.getMonth().getDisplayName(TextStyle.FULL, Locale.getDefault()); - add(downloadLink); - Notification.show("Reporte de horas trabajadas generado exitosamente.", - 3000, Notification.Position.MIDDLE); + headerLayout.add(new Span("Informe " + + String.format("%03d", weekNumber) + "/" + currentYear) {{ + getStyle().set("font-size", "24px"); + getStyle().set("font-weight", "bold"); + }}); - } catch (Exception e) { - Notification.show("Error al generar el reporte de horas trabajadas. Inténtalo de nuevo.", - 3000, Notification.Position.MIDDLE); - e.printStackTrace(); + String teamLeadName = employeeService.getTeamLeadName(team.getId()); + headerLayout.add( + new Span("Asunto: Informe Semanal de Horas Trabajadas") {{ + getStyle().set("font-size", "18px"); + }}, + semanaInfoSpan, + new Span("Horas a cumplir: 40 horas") {{ + getStyle().set("font-size", "18px"); + }}, + new Span("Equipo: " + team.getName()) {{ + getStyle().set("font-size", "18px"); + }}, + new Span("Team Lead: " + teamLeadName) {{ + getStyle().set("font-size", "18px"); + }} + ); } } + private void generateExcelDownloadLink(final List> data, final int weekNumber) { + try { + List headers = List.of("Empleado", + "Horas Trabajadas", "Horas Pendientes", "Observaciones"); + String selectedTeam = equipoComboBox.getValue().getName(); + byte[] excelBytes = reportService.writeAsExcel( + "hours_worked_report", headers, data, selectedTeam, weekNumber, currentYear); + + StreamResource excelResource = new StreamResource("hours_worked_report.xlsx", + () -> new ByteArrayInputStream(excelBytes)); + if (downloadLink == null) { + downloadLink = new Anchor(excelResource, "Descargar Reporte en Excel"); + downloadLink.getElement().setAttribute("download", true); + add(downloadLink); + } else { + downloadLink.setHref(excelResource); + } + } catch (Exception e) { + Notification.show("Error al generar el reporte de horas trabajadas en Excel.", + 3000, Notification.Position.MIDDLE); + } + } + + private int getWeekOfYear(final LocalDate date) { + return date.get(WeekFields.of(Locale.getDefault()).weekOfWeekBasedYear()); + } + } diff --git a/src/main/java/com/primefactorsolutions/views/RequestEmployeeView.java b/src/main/java/com/primefactorsolutions/views/RequestEmployeeView.java index 58b4ab6..0e27062 100644 --- a/src/main/java/com/primefactorsolutions/views/RequestEmployeeView.java +++ b/src/main/java/com/primefactorsolutions/views/RequestEmployeeView.java @@ -4,6 +4,7 @@ import com.primefactorsolutions.model.*; import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.TimeOffRequestService; import com.primefactorsolutions.service.VacationService; +import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.html.Div; @@ -16,16 +17,28 @@ import com.vaadin.flow.router.BeforeEvent; import com.vaadin.flow.router.HasUrlParameter; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.StreamRegistration; +import com.vaadin.flow.server.StreamResource; import com.vaadin.flow.spring.annotation.SpringComponent; import jakarta.annotation.security.PermitAll; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.springframework.context.annotation.Scope; import org.vaadin.firitin.components.grid.PagingGrid; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; import java.time.LocalDate; import java.time.Period; import java.util.*; import java.util.stream.Collectors; +import static com.primefactorsolutions.views.Constants.PAGE_SIZE; + @SpringComponent @PermitAll @Scope("prototype") @@ -96,7 +109,7 @@ public class RequestEmployeeView extends Div implements HasUrlParameter requestGrid.getColumnByKey("daysToBeTake").setHeader("Días a Tomar"); requestGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM); - requestGrid.setPageSize(5); + requestGrid.setPageSize(PAGE_SIZE); requestGrid.asSingleSelect().addValueChangeListener(event -> { TimeOffRequest selectedRequest = event.getValue(); if (selectedRequest != null) { @@ -128,43 +141,47 @@ public class RequestEmployeeView extends Div implements HasUrlParameter Employee employee = employeeService.getEmployee(employeeId); boolean isMale = employee.getGender() == Employee.Gender.MALE; int currentYear = LocalDate.now().getYear(); + LocalDate currentDate = LocalDate.now(); List vacations = vacationService.findVacations(); - double healthLicence = 2; - List healthRequests = requestService - .findByEmployeeAndCategory(employeeId, TimeOffRequestType.PERMISOS_DE_SALUD); - if (healthRequests != null && !healthRequests.isEmpty()) { - healthLicence = healthRequests.getLast().getDaysBalance(); - } + double healthLicence = getHealthLicence(employeeId); double totalFixedAndMovableHolidays = calculateHolidayDays(vacations); double totalPersonalDays = calculatePersonalDays(vacations, isMale); List vacationDays = calculateVacationDays(employee); - double utilizedVacationCurrentDays = vacationDays.get(1); - List vacationCurrentRequests = requestService - .findByEmployeeAndCategory(employeeId, TimeOffRequestType.VACACION_GESTION_ACTUAL); - if (vacationCurrentRequests != null && !vacationCurrentRequests.isEmpty()) { - utilizedVacationCurrentDays = vacationCurrentRequests.getLast().getDaysBalance(); - } - double totalVacationCurrentDays = vacationDays.get(1) - (vacationDays.get(1) - utilizedVacationCurrentDays); - - double utilizedVacationPreviousDays = vacationDays.get(0); - List vacationPreviousRequests = requestService - .findByEmployeeAndCategory(employeeId, TimeOffRequestType.VACACION_GESTION_ANTERIOR); - if (vacationPreviousRequests != null && !vacationPreviousRequests.isEmpty()) { - utilizedVacationPreviousDays = vacationPreviousRequests.getLast().getDaysBalance(); - } - double totalVacationPreviousDays = vacationDays.getFirst() - - (vacationDays.getFirst() - utilizedVacationPreviousDays); + double totalVacationCurrentDays = calculateUtilizedVacationDays( + vacationDays.get(1), + TimeOffRequestType.VACACION_GESTION_ACTUAL + ); + double totalVacationPreviousDays = calculateUtilizedVacationDays( + vacationDays.get(0), + TimeOffRequestType.VACACION_GESTION_ANTERIOR + ); double utilizedFixedAndMovableHolidays = calculateHolidayUtilizedDays(currentYear); double utilizedPersonalDays = calculatePersonalDaysUtilized(isMale, currentYear); - double remainingHolidayDays = totalFixedAndMovableHolidays - utilizedFixedAndMovableHolidays; - double remainingPersonalDays = (totalPersonalDays - utilizedPersonalDays) + healthLicence; - double remainingVacationDays = totalVacationCurrentDays + totalVacationPreviousDays; + double remainingHolidayDays = calculateRemainingHolidayDays( + totalFixedAndMovableHolidays, + utilizedFixedAndMovableHolidays, + employee.getDateOfExit(), + currentDate + ); + double remainingPersonalDays = calculateRemainingPersonalDays( + totalPersonalDays, + utilizedPersonalDays, + healthLicence, + employee.getDateOfExit(), + currentDate + ); + double remainingVacationDays = calculateRemainingVacationDays( + totalVacationCurrentDays, + totalVacationPreviousDays, + employee.getDateOfExit(), + currentDate + ); double totalAvailableDays = remainingHolidayDays + remainingPersonalDays + remainingVacationDays; @@ -176,6 +193,51 @@ public class RequestEmployeeView extends Div implements HasUrlParameter ); } + private double getHealthLicence(final UUID employeeId) { + List healthRequests = requestService + .findByEmployeeAndCategory(employeeId, TimeOffRequestType.PERMISOS_DE_SALUD); + return healthRequests != null && !healthRequests.isEmpty() ? healthRequests.getLast().getDaysBalance() : 2; + } + + private double calculateUtilizedVacationDays(final double vacationDays, final TimeOffRequestType requestType) { + List vacationRequests = requestService.findByEmployeeAndCategory(employeeId, requestType); + if (vacationRequests != null && !vacationRequests.isEmpty()) { + return vacationRequests.getLast().getDaysBalance(); + } + return vacationDays; + } + + private double calculateRemainingVacationDays(final double totalVacationCurrentDays, + final double totalVacationPreviousDays, + final LocalDate exitDate, + final LocalDate currentDate) { + if (exitDate == null || exitDate.isAfter(currentDate)) { + return totalVacationCurrentDays + totalVacationPreviousDays; + } + return 0; + } + + private double calculateRemainingHolidayDays(final double totalFixedAndMovableHolidays, + final double utilizedFixedAndMovableHolidays, + final LocalDate exitDate, + final LocalDate currentDate) { + if (exitDate == null || exitDate.isAfter(currentDate)) { + return totalFixedAndMovableHolidays - utilizedFixedAndMovableHolidays; + } + return 0; + } + + private double calculateRemainingPersonalDays(final double totalPersonalDays, + final double utilizedPersonalDays, + final double healthLicence, + final LocalDate exitDate, + final LocalDate currentDate) { + if (exitDate == null || exitDate.isAfter(currentDate)) { + return (totalPersonalDays - utilizedPersonalDays) + healthLicence; + } + return 0; + } + private double calculateHolidayDays(final List vacations) { return vacations.stream() .filter(req -> req.getType() != Vacation.Type.OTHER) @@ -244,10 +306,8 @@ public class RequestEmployeeView extends Div implements HasUrlParameter private double calculateHolidayUtilizedDays(final int year) { return requests.stream() .filter(this::verificationIsHoliday) - .filter(this::verificationIsHoliday) - .filter(req -> req.getState() == TimeOffRequestStatus.APROBADO) - .filter(req -> req.getState() == TimeOffRequestStatus.EN_USO) - .filter(req -> req.getState() == TimeOffRequestStatus.TOMADO) + .filter(req -> req.getState() == TimeOffRequestStatus.TOMADO + || req.getState() == TimeOffRequestStatus.APROBADO) .filter(req -> getStartDateYear(req) == year) .mapToDouble(TimeOffRequest::getDaysToBeTake) .sum(); @@ -256,9 +316,8 @@ public class RequestEmployeeView extends Div implements HasUrlParameter private double calculatePersonalDaysUtilized(final boolean isMale, final int year) { return requests.stream() .filter(req -> !verificationIsHoliday(req)) - .filter(req -> req.getState() == TimeOffRequestStatus.APROBADO) - .filter(req -> req.getState() == TimeOffRequestStatus.EN_USO) - .filter(req -> req.getState() == TimeOffRequestStatus.TOMADO) + .filter(req -> req.getState() == TimeOffRequestStatus.TOMADO + || req.getState() == TimeOffRequestStatus.APROBADO) .filter(req -> !getStandardExclusions().contains(req.getCategory())) .filter(req -> !(isMale && getMaleSpecificExclusions().contains(req.getCategory()))) .filter(req -> !req.getCategory().name().startsWith("VACACION")) @@ -297,9 +356,10 @@ public class RequestEmployeeView extends Div implements HasUrlParameter private HorizontalLayout createActionButtons() { Button viewButton = createButton("Ver", () -> navigateToViewRequest(request)); Button editButton = createButton("Editar", () -> navigateToEditRequest(request)); + Button downloadButton = new Button("Descargar reporte", event -> downloadReport()); Button closeButton = new Button("Salir", event -> navigateToRequestsListView()); - return new HorizontalLayout(viewButton, editButton, closeButton); + return new HorizontalLayout(viewButton, editButton, downloadButton, closeButton); } private Button createButton(final String caption, final Runnable action) { @@ -409,18 +469,164 @@ public class RequestEmployeeView extends Div implements HasUrlParameter return existingRequest.isEmpty(); } + private String getDateString(final LocalDate date) { + return (date != null) ? date.toString() : ""; + } + + private ByteArrayInputStream generatePdfReport() { + try (PDDocument document = new PDDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { + PDPage page = new PDPage(); + document.addPage(page); + Employee employee = employeeService.getEmployee(employeeId); + + PDPageContentStream contentStream = null; + try { + contentStream = new PDPageContentStream(document, page); + + contentStream.setFont(PDType1Font.TIMES_BOLD, 18); + contentStream.beginText(); + contentStream.newLineAtOffset(200, 750); + contentStream.showText("Reporte de Vacaciones"); + contentStream.endText(); + + contentStream.setFont(PDType1Font.TIMES_ROMAN, 14); + contentStream.beginText(); + contentStream.newLineAtOffset(50, 700); + contentStream.showText("Empleado: " + employee.getFirstName() + " " + employee.getLastName()); + contentStream.newLineAtOffset(0, -15); + contentStream.showText("Equipo: " + employee.getTeam().getName()); + contentStream.endText(); + + float tableTopY = 650; + float margin = 50; + float cellHeight = 20; + String[] headers = {"Categoría", "Estado", "Fecha de Inicio", "Fecha de Fin", "Días a Tomar"}; + int columns = headers.length; + + float[] columnWidths = new float[columns]; + for (int i = 0; i < columns; i++) { + columnWidths[i] = getMaxColumnWidth(headers[i], requests, i); + } + + contentStream.setFont(PDType1Font.TIMES_BOLD, 10); + float currentX = margin; + for (int i = 0; i < columns; i++) { + contentStream.addRect(currentX, tableTopY, columnWidths[i], -cellHeight); + contentStream.beginText(); + contentStream.newLineAtOffset(currentX + 5, tableTopY - 15); + contentStream.showText(headers[i]); + contentStream.endText(); + currentX += columnWidths[i]; + } + contentStream.stroke(); + + contentStream.setFont(PDType1Font.TIMES_ROMAN, 10); + float currentY = tableTopY - cellHeight; + for (TimeOffRequest request : requests) { + String startDate = getDateString(request.getStartDate()); + String endDate = getDateString(request.getEndDate()); + + String[] rowData = { + request.getCategory().name() != null ? request.getCategory().name() : "", + request.getState().name() != null ? request.getState().name() : "", + startDate, + endDate, + String.valueOf(request.getDaysToBeTake() != null ? request.getDaysToBeTake() : 0) + }; + + currentX = margin; + for (int i = 0; i < columns; i++) { + contentStream.addRect(currentX, currentY, columnWidths[i], -cellHeight); + contentStream.beginText(); + contentStream.newLineAtOffset(currentX + 5, currentY - 15); + contentStream.showText(rowData[i]); + contentStream.endText(); + currentX += columnWidths[i]; + } + contentStream.stroke(); + currentY -= cellHeight; + + if (currentY < 50) { + contentStream.close(); + page = new PDPage(); + document.addPage(page); + contentStream = new PDPageContentStream(document, page); + currentY = 750; + } + } + } finally { + if (contentStream != null) { + contentStream.close(); + } + } + document.save(out); + return new ByteArrayInputStream(out.toByteArray()); + } catch (IOException e) { + throw new UncheckedIOException("Error al generar el reporte", e); + } + } + + private float getMaxColumnWidth(final String header, final List requests, final int columnIndex) { + float maxWidth = header.length(); + for (TimeOffRequest request : requests) { + String value = switch (columnIndex) { + case 0 -> request.getCategory().name(); + case 1 -> request.getState().name(); + case 2 -> getDateString(request.getStartDate()); + case 3 -> getDateString(request.getEndDate()); + case 4 -> String.valueOf(request.getDaysToBeTake()); + default -> ""; + }; + if (value != null) { + maxWidth = Math.max(maxWidth, value.length()); + } + } + return maxWidth * 7; + } + + private StreamResource generateVacationReport() { + Employee employee = employeeService.getEmployee(employeeId); + String fileName = String.format("%s_%s-reporte_de_vacaciones_%s.pdf", + employee.getFirstName(), + employee.getLastName(), + LocalDate.now()); + + ByteArrayInputStream pdfStream = generatePdfReport(); + + return new StreamResource(fileName, () -> pdfStream) + .setContentType("application/pdf") + .setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + } + + private void downloadReport() { + StreamResource resource = generateVacationReport(); + getUI().ifPresent(ui -> openDocumentStream(resource, ui)); + } + + private void openDocumentStream(final StreamResource resource, final UI ui) { + StreamRegistration registration = ui.getSession().getResourceRegistry().registerResource(resource); + ui.getPage().open(registration.getResourceUri().toString()); + } + @Override public void setParameter(final BeforeEvent event, final String parameter) { employeeId = UUID.fromString(parameter); Employee employee = employeeService.getEmployee(employeeId); requests = requestService.findRequestsByEmployeeId(employeeId); - setViewTitle(employee.getFirstName() + " " + employee.getLastName(), employee.getTeam().getName()); + setViewTitle( + employee.getFirstName() + " " + employee.getLastName(), + employee.getTeam().getName(), + employee.getDateOfExit() + ); requestGrid.setItems(requests); initializeView(); } - private void setViewTitle(final String employeeName, final String employeeTeam) { + private void setViewTitle(final String employeeName, final String employeeTeam, final LocalDate dateOfExit) { addComponentAsFirst(new H3("Nombre del empleado: " + employeeName)); addComponentAtIndex(1, new H3("Equipo: " + employeeTeam)); + if (dateOfExit != null) { + addComponentAtIndex(2, new H3("Descontado a cero en fecha " + dateOfExit + " por pago de finiquito.")); + } } } diff --git a/src/main/java/com/primefactorsolutions/views/RequestRegisterView.java b/src/main/java/com/primefactorsolutions/views/RequestRegisterView.java index 4d56f10..4f5ae22 100644 --- a/src/main/java/com/primefactorsolutions/views/RequestRegisterView.java +++ b/src/main/java/com/primefactorsolutions/views/RequestRegisterView.java @@ -19,6 +19,7 @@ import jakarta.annotation.security.PermitAll; import org.springframework.context.annotation.Scope; import java.time.LocalDate; +import java.time.DayOfWeek; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Comparator; @@ -91,8 +92,25 @@ public class RequestRegisterView extends VerticalLayout { onCategoryChange(event.getValue()); handleCategorySelection(event.getValue()); }); - startDatePicker.addValueChangeListener(event -> updateDatePickerMinValues()); - endDatePicker.addValueChangeListener(event -> calculateDays()); + startDatePicker.addValueChangeListener(event -> { + LocalDate selectedDate = event.getValue(); + if (selectedDate != null && (selectedDate.getDayOfWeek().getValue() == 6 + || selectedDate.getDayOfWeek().getValue() == 7)) { + startDatePicker.setValue(selectedDate.minusDays(1)); + } + updateDatePickerMinValues(); + }); + endDatePicker.addValueChangeListener(event -> { + if (startDatePicker.getValue() != null) { + endDatePicker.setMin(startDatePicker.getValue()); + } + LocalDate selectedDate = event.getValue(); + if (selectedDate != null && (selectedDate.getDayOfWeek().getValue() == 6 + || selectedDate.getDayOfWeek().getValue() == 7)) { + endDatePicker.setValue(selectedDate.minusDays(1)); + } + calculateDays(); + }); } private void configureBinder() { @@ -167,7 +185,7 @@ public class RequestRegisterView extends VerticalLayout { private void onCategoryChange(final TimeOffRequestType selectedCategory) { if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ACTUAL - || selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) { + || selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) { startDatePicker.setEnabled(true); endDatePicker.setEnabled(true); } else { @@ -178,6 +196,10 @@ public class RequestRegisterView extends VerticalLayout { private boolean isCategoryAvailable(final List employeeRequests, final TimeOffRequestType category) { + if (category == TimeOffRequestType.CUMPLEAÑOS && employee.getBirthday() == null) { + return false; + } + List requestsByCategory = employeeRequests.stream() .filter(request -> request.getCategory() == category) .toList(); @@ -199,8 +221,8 @@ public class RequestRegisterView extends VerticalLayout { && latestRequest.getDaysBalance() > 0) || latestRequest.getState() == TimeOffRequestStatus.RECHAZADO || (latestRequest.getState() == TimeOffRequestStatus.TOMADO - && latestRequest.getDaysBalance() == 0 - && latestRequest.getExpiration().isBefore(LocalDate.now())); + && latestRequest.getDaysBalance() == 0 + && latestRequest.getExpiration().isBefore(LocalDate.now())); } else { return (latestRequest.getState() == TimeOffRequestStatus.TOMADO && latestRequest.getExpiration().isBefore(LocalDate.now())) @@ -375,6 +397,13 @@ public class RequestRegisterView extends VerticalLayout { Double availableDays = availableDaysField.getValue(); if (areDatesValid(startDate, endDate)) { + long workDays = countWorkDaysBetween(startDate, endDate); + + daysToBeTakenField.setValue((double) workDays); + + balanceDaysField.setValue(availableDaysField.getValue() - workDays); + + double daysToBeTaken = calculateDaysBetween(startDate, endDate); setDaysToBeTakenField(daysToBeTaken); @@ -384,6 +413,12 @@ public class RequestRegisterView extends VerticalLayout { if (balanceDays < 0.0) { clearFields(); } + + if (daysToBeTakenField.getValue() > 10 + && (categoryComboBox.getValue() == TimeOffRequestType.VACACION_GESTION_ANTERIOR + || categoryComboBox.getValue() == TimeOffRequestType.VACACION_GESTION_ACTUAL)) { + clearFields(); + } } } @@ -391,8 +426,19 @@ public class RequestRegisterView extends VerticalLayout { return startDate != null && endDate != null; } + private long countWorkDaysBetween(final LocalDate startDate, final LocalDate endDate) { + return startDate.datesUntil(endDate.plusDays(1)) + .filter(date -> date.getDayOfWeek() != DayOfWeek.SATURDAY && date.getDayOfWeek() != DayOfWeek.SUNDAY) + .count(); + } + private double calculateDaysBetween(final LocalDate startDate, final LocalDate endDate) { - return java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate) + 1; + return startDate.datesUntil(endDate.plusDays(1)) + .filter(date -> { + DayOfWeek day = date.getDayOfWeek(); + return day != DayOfWeek.SATURDAY && day != DayOfWeek.SUNDAY; + }) + .count(); } private void setDaysToBeTakenField(final double daysToBeTaken) { @@ -456,9 +502,16 @@ public class RequestRegisterView extends VerticalLayout { handleExistingRequests(request); } - requestService.saveTimeOffRequest(request); - Notification.show("Solicitud guardada correctamente."); - closeForm(); + long differentDays = ChronoUnit.DAYS.between(LocalDate.now(), request.getStartDate()); + if (differentDays >= -15 && differentDays <= 90) { + requestService.saveTimeOffRequest(request); + Notification.show("Solicitud guardada correctamente."); + closeForm(); + } else { + Notification.show( + "La fecha de inicio debe encontrarse dentro del rango de 15 días a 3 meses de anticipación." + ); + } } private TimeOffRequest prepareRequest() { diff --git a/src/main/java/com/primefactorsolutions/views/RequestsListView.java b/src/main/java/com/primefactorsolutions/views/RequestsListView.java index 4b770e1..c911edc 100644 --- a/src/main/java/com/primefactorsolutions/views/RequestsListView.java +++ b/src/main/java/com/primefactorsolutions/views/RequestsListView.java @@ -5,29 +5,39 @@ import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.TeamService; import com.primefactorsolutions.service.TimeOffRequestService; import com.primefactorsolutions.service.VacationService; +import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.html.Main; import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.StreamRegistration; +import com.vaadin.flow.server.StreamResource; import com.vaadin.flow.spring.annotation.SpringComponent; import jakarta.annotation.security.PermitAll; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.context.annotation.Scope; import org.vaadin.firitin.components.grid.PagingGrid; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; import java.time.LocalDate; import java.time.Period; import java.util.*; import java.util.stream.Collectors; +import static com.primefactorsolutions.views.Constants.PAGE_SIZE; + @SpringComponent @Scope("prototype") @PageTitle("Requests") @Route(value = "/requests", layout = MainLayout.class) @PermitAll -public class RequestsListView extends Main { +public class RequestsListView extends BaseView { private final TimeOffRequestService requestService; private final EmployeeService employeeService; @@ -35,10 +45,8 @@ public class RequestsListView extends Main { private final VacationService vacationService; private final PagingGrid requestGrid = new PagingGrid<>(); - private List employees = Collections.emptyList(); private ComboBox employeeFilter; private ComboBox teamFilter; - private ComboBox categoryFilter; private ComboBox stateFilter; private UUID selectedEmployeeId; @@ -51,7 +59,6 @@ public class RequestsListView extends Main { this.employeeService = employeeService; this.teamService = teamService; this.vacationService = vacationService; - this.employees = employeeService.findAllEmployees(); initializeView(); refreshGeneralRequestGrid(null, null, null); } @@ -60,14 +67,16 @@ public class RequestsListView extends Main { requestService.updateRequestStatuses(); setupFilters(); setupRequestGrid(); - add(requestGrid); - add(createActionButtons()); + getCurrentPageLayout().add(requestGrid); + getCurrentPageLayout().add(createActionButtons()); } private void setupFilters() { - add(createEmployeeFilter()); - add(createTeamFilter()); - add(createStateFilter()); + final HorizontalLayout hl = new HorizontalLayout(); + hl.add(createEmployeeFilter()); + hl.add(createTeamFilter()); + hl.add(createStateFilter()); + getCurrentPageLayout().add(hl); } private void setupRequestGrid() { @@ -77,7 +86,7 @@ public class RequestsListView extends Main { requestGrid.addColumn(this::getGeneralTotal).setHeader("Total general"); requestGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM); - requestGrid.setPageSize(5); + requestGrid.setPageSize(PAGE_SIZE); requestGrid.asSingleSelect().addValueChangeListener(event -> { Employee selectedRequest = event.getValue(); if (selectedRequest != null) { @@ -94,8 +103,9 @@ public class RequestsListView extends Main { Notification.show("Seleccione una solicitud.", 3000, Notification.Position.MIDDLE); } }); + Button downloadButton = new Button("Descargar reporte", event -> downloadReport()); Button closeButton = new Button("Salir", event -> navigateToMainView()); - return new HorizontalLayout(viewButton, closeButton); + return new HorizontalLayout(viewButton, downloadButton, closeButton); } private void refreshGeneralRequestGrid(final Employee employee, @@ -158,7 +168,7 @@ public class RequestsListView extends Main { private String getEmployeeStatus(final Employee employee) { Optional activeRequest = requestService .findByEmployeeAndState(employee.getId(), TimeOffRequestStatus.EN_USO); - return activeRequest.isPresent() ? "EN_DESCANSO" : "ACTIVO"; + return activeRequest.isPresent() ? "EN_DESCANSO" : "EN_FUNCIONES"; } private String getGeneralTotal(final Employee employee) { @@ -190,6 +200,13 @@ public class RequestsListView extends Main { double totalAvailable = calculateTotalAvailable(vacations, employeeRequests, employee); double generalTotal = totalAvailable + totalVacations - totalUtilized; + + if (employee.getDateOfExit() != null + && (employee.getDateOfExit().isBefore(LocalDate.now()) + || employee.getDateOfExit().isEqual(LocalDate.now()))) { + generalTotal = 0; + } + return String.valueOf(generalTotal); } @@ -216,6 +233,8 @@ public class RequestsListView extends Main { int currentYear = LocalDate.now().getYear(); return employeeRequests.stream() .filter(Objects::nonNull) + .filter(request -> request.getState() == TimeOffRequestStatus.APROBADO + || request.getState() == TimeOffRequestStatus.TOMADO) .filter(request -> request.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD) .filter(request -> request.getCategory() != TimeOffRequestType.VACACION_GESTION_ACTUAL) .filter(request -> request.getCategory() != TimeOffRequestType.VACACION_GESTION_ANTERIOR) @@ -328,10 +347,8 @@ public class RequestsListView extends Main { && !employeeRequestCategories.contains(vacation.getCategory())) { return false; } - if (!isFemale(employee) && genderSpecificExclusions.contains(vacation.getCategory())) { - return false; - } - return true; + + return isFemale(employee) || !genderSpecificExclusions.contains(vacation.getCategory()); } private boolean isFemale(final Employee employee) { @@ -389,7 +406,7 @@ public class RequestsListView extends Main { private enum Status { TODOS, EN_DESCANSO, - ACTIVO + EN_FUNCIONES } private Employee createAllEmployeesOption() { @@ -411,4 +428,50 @@ public class RequestsListView extends Main { private void navigateToTimeOffRequestView(final UUID idEmployee) { getUI().ifPresent(ui -> ui.navigate("requests/" + idEmployee.toString())); } + + private ByteArrayInputStream generateExcelReport(final List employees) { + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("REPORTE_GENERAL_DE_VACACIONES"); + Row headerRow = sheet.createRow(0); + + String[] headers = {"Empleado", "Equipo", "Estado", "Total Horas"}; + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + } + + int rowIndex = 1; + for (Employee employee : employees) { + Row row = sheet.createRow(rowIndex++); + row.createCell(0).setCellValue(getEmployeeFullName(employee)); + row.createCell(1).setCellValue(getTeamName(employee)); + row.createCell(2).setCellValue(getEmployeeStatus(employee)); + row.createCell(3).setCellValue(getGeneralTotal(employee)); + } + + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + workbook.write(out); + return new ByteArrayInputStream(out.toByteArray()); + } + } catch (IOException e) { + throw new UncheckedIOException("Error al generar el archivo Excel", e); + } + } + + private StreamResource generateGeneralVacationReport() { + List employees = employeeService.findAllEmployees(); + ByteArrayInputStream excelStream = generateExcelReport(employees); + return new StreamResource("reporte_general_de_vacaciones_" + LocalDate.now() + ".xlsx", + () -> excelStream); + } + + private void downloadReport() { + StreamResource resource = generateGeneralVacationReport(); + getUI().ifPresent(ui -> openDocumentStream(resource, ui)); + } + + private void openDocumentStream(final StreamResource resource, final UI ui) { + StreamRegistration registration = ui.getSession().getResourceRegistry().registerResource(resource); + ui.getPage().open(registration.getResourceUri().toString()); + } } \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 5cb7a98..8fc10fc 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -90,3 +90,6 @@ insert into time_off_request (id, version, employee_id, category, state, availab values ('12ec8b74-983d-4a17-b67e-134f45ae904c', 1, '5c1a7b82-832d-4f24-8377-54b77b91b6a8', 'AÑO_NUEVO', 'SOLICITADO', 1, '2025-01-01', '2025-01-01', '2025-01-01', 1, 0); insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance) values ('89bc4b2a-943f-487c-a9f3-bacf78145e67', 1, 'cba3efb7-32bc-44be-9fdc-fc5e4f211254', 'LUNES_CARNAVAL', 'APROBADO', 1, '2024-02-12', '2024-02-12', '2024-02-12', 1, 0); + +INSERT INTO HOURS_WORKED (ID, VERSION, ACTIVIDAD, DATE, HORAS_TAREAS_ESPECIFICAS, HORASPENDIENTES, HOURS, TAREAS_ESPECIFICAS, TOTAL_HOURS, WEEK_NUMBER, EMPLOYEE_ID, TEAM_ID) +VALUES ('6d6b3a71-9b11-4526-8335-b089627a8cd6', 0, 'Scrum Meeting', '2024-11-15', 0.0, 0.0, 2.0, NULL, 8, 46, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa');