timesheet adjustments

This commit is contained in:
JaiPandey425 2025-11-17 20:04:42 +05:30
parent f0e6b704cd
commit df92e68a0a
11 changed files with 497 additions and 150 deletions

BIN
appIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="" /> --> <!-- <link rel="icon" type="image/svg+xml" href="" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VA Tracker</title> <title>DocVa</title>
<!-- <meta <!-- <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self';" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self';"

76
package-lock.json generated
View File

@ -78,6 +78,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.28.5",
@ -2870,6 +2871,7 @@
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
@ -3139,6 +3141,7 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -3341,7 +3344,6 @@
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"archiver-utils": "^2.1.0", "archiver-utils": "^2.1.0",
"async": "^3.2.4", "async": "^3.2.4",
@ -3361,7 +3363,6 @@
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"glob": "^7.1.4", "glob": "^7.1.4",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
@ -3383,8 +3384,7 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/archiver-utils/node_modules/readable-stream": { "node_modules/archiver-utils/node_modules/readable-stream": {
"version": "2.3.8", "version": "2.3.8",
@ -3392,7 +3392,6 @@
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
@ -3408,8 +3407,7 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/archiver-utils/node_modules/string_decoder": { "node_modules/archiver-utils/node_modules/string_decoder": {
"version": "1.1.1", "version": "1.1.1",
@ -3417,7 +3415,6 @@
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
@ -3702,6 +3699,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.25", "baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754", "caniuse-lite": "^1.0.30001754",
@ -4319,7 +4317,6 @@
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"buffer-crc32": "^0.2.13", "buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.2", "crc32-stream": "^4.0.2",
@ -4521,7 +4518,6 @@
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"crc32": "bin/crc32.njs" "crc32": "bin/crc32.njs"
}, },
@ -4535,7 +4531,6 @@
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"crc-32": "^1.2.0", "crc-32": "^1.2.0",
"readable-stream": "^3.4.0" "readable-stream": "^3.4.0"
@ -4969,6 +4964,7 @@
"integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"app-builder-lib": "25.1.8", "app-builder-lib": "25.1.8",
"builder-util": "25.1.7", "builder-util": "25.1.7",
@ -5188,7 +5184,6 @@
"integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"app-builder-lib": "25.1.8", "app-builder-lib": "25.1.8",
"archiver": "^5.3.1", "archiver": "^5.3.1",
@ -5202,7 +5197,6 @@
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1", "jsonfile": "^6.0.1",
@ -5218,7 +5212,6 @@
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"universalify": "^2.0.0" "universalify": "^2.0.0"
}, },
@ -5232,7 +5225,6 @@
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
@ -5619,16 +5611,6 @@
"devOptional": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"license": "MIT",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/end-of-stream": { "node_modules/end-of-stream": {
"version": "1.4.5", "version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@ -6179,8 +6161,7 @@
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/fs-extra": { "node_modules/fs-extra": {
"version": "8.1.0", "version": "8.1.0",
@ -7831,7 +7812,6 @@
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"readable-stream": "^2.0.5" "readable-stream": "^2.0.5"
}, },
@ -7844,8 +7824,7 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/lazystream/node_modules/readable-stream": { "node_modules/lazystream/node_modules/readable-stream": {
"version": "2.3.8", "version": "2.3.8",
@ -7853,7 +7832,6 @@
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
@ -7869,8 +7847,7 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/lazystream/node_modules/string_decoder": { "node_modules/lazystream/node_modules/string_decoder": {
"version": "1.1.1", "version": "1.1.1",
@ -7878,7 +7855,6 @@
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
@ -8159,40 +8135,35 @@
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/lodash.difference": { "node_modules/lodash.difference": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/lodash.flatten": { "node_modules/lodash.flatten": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/lodash.isplainobject": { "node_modules/lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/lodash.union": { "node_modules/lodash.union": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
@ -8997,7 +8968,6 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -9677,8 +9647,7 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/progress": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
@ -9773,6 +9742,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@ -9785,6 +9755,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@ -9804,6 +9775,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/use-sync-external-store": "^0.0.6", "@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0" "use-sync-external-store": "^1.4.0"
@ -9950,7 +9922,6 @@
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"minimatch": "^5.1.0" "minimatch": "^5.1.0"
} }
@ -9961,7 +9932,6 @@
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@ -9972,7 +9942,6 @@
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
@ -10016,7 +9985,8 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/redux-thunk": { "node_modules/redux-thunk": {
"version": "3.1.0", "version": "3.1.0",
@ -11293,7 +11263,6 @@
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"bl": "^4.0.3", "bl": "^4.0.3",
"end-of-stream": "^1.4.1", "end-of-stream": "^1.4.1",
@ -11764,6 +11733,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",
@ -12324,7 +12294,6 @@
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"archiver-utils": "^3.0.4", "archiver-utils": "^3.0.4",
"compress-commons": "^4.1.2", "compress-commons": "^4.1.2",
@ -12340,7 +12309,6 @@
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"glob": "^7.2.3", "glob": "^7.2.3",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

View File

@ -226,6 +226,7 @@ ipcMain.handle("forgot-password", async (event, email: string) => {
}; };
} }
}); });
ipcMain.handle('get-running-apps', async () => { ipcMain.handle('get-running-apps', async () => {
return await getRunningAppsList() return await getRunningAppsList()
}); });
@ -235,7 +236,6 @@ ipcMain.handle('capture-specific-window', async (event, appTitle: string) => {
return await captureSpecificApp(appTitle) return await captureSpecificApp(appTitle)
}); });
ipcMain.handle("get-profile", async () => { ipcMain.handle("get-profile", async () => {
try { try {
const response = await api.get("/profile"); const response = await api.get("/profile");
@ -253,7 +253,6 @@ ipcMain.handle("get-profile", async () => {
} }
}); });
ipcMain.handle("change-password", async (event, data) => { ipcMain.handle("change-password", async (event, data) => {
console.log("data-change-password", data) console.log("data-change-password", data)
@ -281,7 +280,6 @@ ipcMain.handle("change-password", async (event, data) => {
} }
}); });
ipcMain.handle("get-project-summary", async (_, { projectId, date }) => { ipcMain.handle("get-project-summary", async (_, { projectId, date }) => {
try { try {
const url = `/timesheet/summary/${projectId}/${date}`; const url = `/timesheet/summary/${projectId}/${date}`;
@ -301,6 +299,30 @@ ipcMain.handle("get-project-summary", async (_, { projectId, date }) => {
} }
}); });
ipcMain.handle("create-timesheet", async (event, data) => {
console.log("data" , data)
try {
console.log("📩 Timesheet Payload:", data);
const res = await api.post("/timesheet/adjustment/request", data);
console.log(res , " Line 323")
return {
success: true,
message: "Timesheet created successfully",
data: res,
};
} catch (error:any) {
console.error("❌ Timesheet Error:", error);
return {
success: false,
message: error.response?.data?.message || "Failed to create timesheet",
};
}
})
export const sendTokenExpired = () => { export const sendTokenExpired = () => {
@ -312,3 +334,6 @@ export const sendTokenExpired = () => {
} }
}); });
}; };
;

View File

@ -13,6 +13,20 @@ app.setAppUserModelId("com.tracker.app");
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
function resolveIcon() {
const isDev = !app.isPackaged;
if (isDev) {
// DEV mode → use project root path
return path.join(process.cwd(), "src", "assets", "icons", "appIcon.ico");
}
console.log("Line 24", path.join(__dirname, "..", "assets", "icons", "appIcon.ico"))
// PRODUCTION mode → inside dist-electron
return path.join(__dirname, "..", "assets", "icons", "appIcon.ico");
}
// --------------------------- // ---------------------------
// SINGLE INSTANCE LOCK // SINGLE INSTANCE LOCK
// --------------------------- // ---------------------------
@ -53,7 +67,7 @@ app.on("ready", async () => {
show: true, show: true,
skipTaskbar: true, skipTaskbar: true,
webPreferences: { webPreferences: {
devTools: false, devTools: true,
} }
}); });
@ -62,6 +76,11 @@ app.on("ready", async () => {
path.join(__dirname, "../assets/splash.html") path.join(__dirname, "../assets/splash.html")
); );
// Create the main window // Create the main window
const win = new BrowserWindow({ const win = new BrowserWindow({
width: 500, width: 500,
@ -76,8 +95,9 @@ app.on("ready", async () => {
autoHideMenuBar: true, autoHideMenuBar: true,
frame: process.platform === "darwin" ? false : true, frame: process.platform === "darwin" ? false : true,
show: false, // speed improvement show: false, // speed improvement
icon: resolveIcon(),
webPreferences: { webPreferences: {
devTools: false, devTools: true,
preload: getPreloadPath(), preload: getPreloadPath(),
contextIsolation: true, contextIsolation: true,
nodeIntegration: false, nodeIntegration: false,
@ -101,7 +121,7 @@ app.on("ready", async () => {
// ------------------------------------ // ------------------------------------
if (isDev()) { if (isDev()) {
win.loadURL("http://localhost:5123"); win.loadURL("http://localhost:5123");
// win.webContents.openDevTools(); // Optional (slows startup) win.webContents.openDevTools(); // Optional (slows startup)
} else { } else {
win.loadFile(getUIPath()); win.loadFile(getUIPath());
} }
@ -128,17 +148,17 @@ app.on("ready", async () => {
}); });
app.on('browser-window-focus', () => { // app.on('browser-window-focus', () => {
globalShortcut.register('CommandOrControl+R', () => { // globalShortcut.register('CommandOrControl+R', () => {
// Do nothing; disables reload // // Do nothing; disables reload
}); // });
globalShortcut.register('F5', () => { // globalShortcut.register('F5', () => {
// Do nothing; disables reload // // Do nothing; disables reload
}); // });
globalShortcut.register('CommandOrControl+Shift+I', () => { // globalShortcut.register('CommandOrControl+Shift+I', () => {
// Do nothing; disables DevTools // // Do nothing; disables DevTools
}); // });
}); // });
app.on('browser-window-blur', () => { app.on('browser-window-blur', () => {
globalShortcut.unregisterAll(); globalShortcut.unregisterAll();

View File

@ -13,7 +13,7 @@ contextBridge.exposeInMainWorld('electron', {
resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height), resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height),
getProjectList: () => ipcRenderer.invoke("get-project-list"), getProjectList: () => ipcRenderer.invoke("get-project-list"),
getRunningApps: () => ipcRenderer.invoke('get-running-apps'), getRunningApps: () => ipcRenderer.invoke('get-running-apps'),
captureAppScreenShot: (appTitle: string) : Promise<string> => ipcRenderer.invoke('capture-specific-window', appTitle), captureAppScreenShot: (appTitle: string): Promise<string> => ipcRenderer.invoke('capture-specific-window', appTitle),
onWindowUpdate: (callback) => onWindowUpdate: (callback) =>
ipcRenderer.on("window:updated", (_, data) => callback(data)), ipcRenderer.on("window:updated", (_, data) => callback(data)),
@ -45,10 +45,11 @@ contextBridge.exposeInMainWorld('electron', {
}); });
}, },
getProjectSummary: (projectId: string, date: string) => getProjectSummary: (projectId: string, date: string) =>
ipcRenderer.invoke("get-project-summary", { projectId, date }), ipcRenderer.invoke("get-project-summary", { projectId, date }),
createTimesheet: (data: any) => ipcRenderer.invoke('create-timesheet', data),
} satisfies Window['electron']); } satisfies Window['electron']);
// function ipcInvoke<Key extends keyof EventPayloadMapping>( // function ipcInvoke<Key extends keyof EventPayloadMapping>(

View File

@ -268,7 +268,7 @@ const Dashboard: React.FC = () => {
<tr> <tr>
<th>#</th> <th>#</th>
<th>Name</th> <th>Name</th>
<th>Logs Hours</th> {/* <th>Logs Hours</th> */}
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
@ -282,9 +282,9 @@ const Dashboard: React.FC = () => {
<tr key={project.projectId}> <tr key={project.projectId}>
<td className="text-xs opacity-70">{index + 1}</td> <td className="text-xs opacity-70">{index + 1}</td>
<td className="font-medium">{project.projectName}</td> <td className="font-medium">{project.projectName}</td>
<td className="font-mono text-xs"> {/* <td className="font-mono text-xs">
{formatWorkTime(project.totalWorkTime)} {formatWorkTime(project.totalWorkTime)}
</td> </td> */}
<td> <td>
<button <button
className={`btn btn-xs ${isCurrentlySelected ? "btn-success" : "btn-primary" className={`btn btn-xs ${isCurrentlySelected ? "btn-success" : "btn-primary"

View File

@ -44,6 +44,15 @@ const data: LogEntry[] = [
type TabType = 'create_and_adjust_timesheet' | 'sick' | 'adjustment'; type TabType = 'create_and_adjust_timesheet' | 'sick' | 'adjustment';
// Validation interface
interface FormErrors {
selectedProjectId?: string;
selectedDate?: string;
startTime?: string;
endTime?: string;
general?: string;
}
const LeaveAndAdjustments: React.FC = () => { const LeaveAndAdjustments: React.FC = () => {
const [activeTab, setActiveTab] = useState<TabType>('create_and_adjust_timesheet'); const [activeTab, setActiveTab] = useState<TabType>('create_and_adjust_timesheet');
const projects = useSelector((state: RootState) => state.projects.projectsData); const projects = useSelector((state: RootState) => state.projects.projectsData);
@ -65,6 +74,12 @@ const LeaveAndAdjustments: React.FC = () => {
const [sickErrors, setSickErrors] = useState<Partial<LeaveFormData>>({}); const [sickErrors, setSickErrors] = useState<Partial<LeaveFormData>>({});
const [selectedProjectId, setSelectedProjectId] = useState("") const [selectedProjectId, setSelectedProjectId] = useState("")
const [selectedDate, setSelectedDate] = useState("") const [selectedDate, setSelectedDate] = useState("")
const [totalAdjustment, setTotalAdjustment] = useState("0h 0m");
const [pendingOverride, setPendingOverride] = useState<string | null>(null);
const [loadingSummary, setLoadingSummary] = useState(false);
// Form Validation State
const [formErrors, setFormErrors] = useState<FormErrors>({});
// Project Summary State // Project Summary State
const [projectSummary, setProjectSummary] = useState<ProjectSummaryResponse | null>(null); const [projectSummary, setProjectSummary] = useState<ProjectSummaryResponse | null>(null);
@ -116,6 +131,120 @@ const LeaveAndAdjustments: React.FC = () => {
} }
}; };
// Field validation functions
const validateProjectId = (projectId: string): string | undefined => {
if (!projectId) return 'Project selection is required';
return undefined;
};
const validateDate = (date: string): string | undefined => {
if (!date) return 'Date is required';
const selectedDateObj = new Date(date);
const today = new Date();
today.setHours(0, 0, 0, 0);
if (selectedDateObj > today) return 'Cannot select future dates';
return undefined;
};
const validateTime = (time: string, fieldName: string): string | undefined => {
if (!time) return `${fieldName} is required`;
const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/;
if (!timeRegex.test(time)) return 'Invalid time format';
return undefined;
};
const validateTimeRange = (start: string, end: string): string | undefined => {
if (!start || !end) return undefined;
const startTime = DateTime.fromFormat(start, "HH:mm");
const endTime = DateTime.fromFormat(end, "HH:mm");
if (!startTime.isValid || !endTime.isValid) return 'Invalid time values';
if (endTime <= startTime) return 'End time must be after start time';
return undefined;
};
const validateDedicatedHours = (start: string, end: string): string | undefined => {
if (!start || !end || !projectSummary) return undefined;
const startTime = DateTime.fromFormat(start, "HH:mm");
const endTime = DateTime.fromFormat(end, "HH:mm");
if (!startTime.isValid || !endTime.isValid) return undefined;
const diff = endTime.diff(startTime, ["hours", "minutes"]).toObject();
const workedHours = (diff.hours || 0) + (diff.minutes || 0) / 60;
if (workedHours > projectSummary.dedicatedHours) {
return `Cannot exceed project's dedicated hours (${projectSummary.dedicatedHours} hrs)`;
}
return undefined;
};
// Real-time field validation
const validateField = (field: keyof FormErrors, value: string) => {
let error: string | undefined;
switch (field) {
case 'selectedProjectId':
error = validateProjectId(value);
break;
case 'selectedDate':
error = validateDate(value);
break;
case 'startTime':
error = validateTime(value, 'Start time');
if (!error && endTime) {
error = validateTimeRange(value, endTime) || validateDedicatedHours(value, endTime);
}
break;
case 'endTime':
error = validateTime(value, 'End time');
if (!error && startTime) {
error = validateTimeRange(startTime, value) || validateDedicatedHours(startTime, value);
}
break;
}
setFormErrors(prev => ({
...prev,
[field]: error
}));
return !error; // Return true if valid
};
// Comprehensive form validation
const validateForm = (): boolean => {
const errors: FormErrors = {};
// Validate individual fields
errors.selectedProjectId = validateProjectId(selectedProjectId);
errors.selectedDate = validateDate(selectedDate);
errors.startTime = validateTime(startTime, 'Start time');
errors.endTime = validateTime(endTime, 'End time');
// Validate time range and dedicated hours
if (!errors.startTime && !errors.endTime) {
errors.startTime = validateTimeRange(startTime, endTime);
if (!errors.startTime) {
errors.startTime = validateDedicatedHours(startTime, endTime);
}
}
// Filter out undefined errors
const filteredErrors = Object.fromEntries(
Object.entries(errors).filter(([_, value]) => value !== undefined)
) as FormErrors;
setFormErrors(filteredErrors);
return Object.keys(filteredErrors).length === 0;
};
const handlePtoSubmit = async () => { const handlePtoSubmit = async () => {
const errors = validateLeaveForm(ptoLeave); const errors = validateLeaveForm(ptoLeave);
@ -166,6 +295,8 @@ const LeaveAndAdjustments: React.FC = () => {
const [editingEntry, setEditingEntry] = useState<AdjustmentEntry | null>(null); const [editingEntry, setEditingEntry] = useState<AdjustmentEntry | null>(null);
const [newHours, setNewHours] = useState<number>(0); const [newHours, setNewHours] = useState<number>(0);
const [comment, setComment] = useState<string>("")
const [isSubmitting, setIsSubmitting] = useState(false);
const handlePeriodChange = (value: string) => setSelectedPeriod(value); const handlePeriodChange = (value: string) => setSelectedPeriod(value);
@ -189,15 +320,16 @@ const LeaveAndAdjustments: React.FC = () => {
const handleProjectSelect = (projectId: string) => { const handleProjectSelect = (projectId: string) => {
console.log("handleProjectSelect", projectId) console.log("handleProjectSelect", projectId)
setSelectedProjectId(projectId) setSelectedProjectId(projectId);
validateField('selectedProjectId', projectId);
} }
const handleSelectedDate = (value: string) => { const handleSelectedDate = (value: string) => {
setSelectedDate(value); setSelectedDate(value);
validateField('selectedDate', value);
}; };
const debugTimezoneInfo = (utcTimeString: string, check: string) => {
const debugTimezoneInfo = (utcTimeString: string , check : string) => {
const utcTime = DateTime.fromISO(utcTimeString, { zone: 'utc' }); const utcTime = DateTime.fromISO(utcTimeString, { zone: 'utc' });
const localTime = utcTime.toLocal(); const localTime = utcTime.toLocal();
@ -206,13 +338,12 @@ const LeaveAndAdjustments: React.FC = () => {
console.log('Local Time:', localTime.toString()); console.log('Local Time:', localTime.toString());
console.log('System Timezone:', localTime.zoneName); console.log('System Timezone:', localTime.zoneName);
console.log('Formatted Local Time:', localTime.toFormat('HH:mm')); console.log('Formatted Local Time:', localTime.toFormat('HH:mm'));
// return localTime.toFormat('HH:mm');
}; };
const fetchLogs = async () => { const fetchLogs = async () => {
try { try {
console.log("Sending =>", selectedProjectId, selectedDate); console.log("Sending =>", selectedProjectId, selectedDate);
setLoadingSummary(true);
const res = await window.electron.getProjectSummary( const res = await window.electron.getProjectSummary(
selectedProjectId, selectedProjectId,
@ -227,13 +358,13 @@ const LeaveAndAdjustments: React.FC = () => {
// Convert UTC times to local timezone and set them // Convert UTC times to local timezone and set them
if (res?.data?.summary?.firstLoginTime) { if (res?.data?.summary?.firstLoginTime) {
const localStartTime = convertToLocalTime(res?.data?.summary?.firstLoginTime); const localStartTime = convertToLocalTime(res?.data?.summary?.firstLoginTime);
debugTimezoneInfo(localStartTime ,"start"); debugTimezoneInfo(localStartTime, "start");
setStartTime(localStartTime); setStartTime(localStartTime);
} }
if (res?.data?.summary?.lastLogoutTime) { if (res?.data?.summary?.lastLogoutTime) {
const localEndTime = convertToLocalTime(res?.data?.summary?.lastLogoutTime); const localEndTime = convertToLocalTime(res?.data?.summary?.lastLogoutTime);
debugTimezoneInfo(localEndTime , "end"); debugTimezoneInfo(localEndTime, "end");
setEndTime(localEndTime); setEndTime(localEndTime);
} }
} else { } else {
@ -247,6 +378,8 @@ const LeaveAndAdjustments: React.FC = () => {
setProjectSummary(null); setProjectSummary(null);
setStartTime(""); setStartTime("");
setEndTime(""); setEndTime("");
} finally {
setLoadingSummary(false);
} }
}; };
@ -260,16 +393,160 @@ const LeaveAndAdjustments: React.FC = () => {
} }
}, [selectedProjectId, selectedDate]); }, [selectedProjectId, selectedDate]);
// Auto calculations --- derived state
useEffect(() => {
if (!projectSummary) return; // No API summary yet
if (!startTime || !endTime) return;
// Clear previous time range errors when times change
if (formErrors.startTime?.includes('time') || formErrors.startTime?.includes('dedicated')) {
setFormErrors(prev => ({
...prev,
startTime: undefined
}));
}
// 1. Validation: prevent end < start
const start = DateTime.fromFormat(startTime, "HH:mm");
const end = DateTime.fromFormat(endTime, "HH:mm");
if (!start.isValid || !end.isValid) return;
if (end < start) {
setTotalAdjustment("0h 0m");
return;
}
// 2. Calculate worked minutes from UI input
const diff = end.diff(start, ["hours", "minutes"]).toObject();
const workedHours = diff.hours || 0;
const workedMinutes = diff.minutes || 0;
const totalWorkedDecimal = workedHours + workedMinutes / 60;
// 3. Compare with server original total
const originalWorkedDecimal = Number(projectSummary.totalWorkedHoursDecimal);
const adjustment = totalWorkedDecimal - originalWorkedDecimal;
// 4. Build readable format (always positive for display)
const adjH = Math.floor(Math.abs(adjustment));
const adjM = Math.round((Math.abs(adjustment) % 1) * 60);
const formatted = `${adjustment >= 0 ? "+" : "-"}${adjH}h ${adjM}m`;
setTotalAdjustment(formatted);
// 5. Update Pending Hours -> dedicated - new worked
const newPending = projectSummary.dedicatedHours - totalWorkedDecimal;
const newPendingRounded = newPending.toFixed(2);
setPendingOverride(newPendingRounded);
}, [startTime, endTime, projectSummary]);
const handleStartTimeChange = (value: string) => {
console.log("Start changed:", value);
setStartTime(value);
validateField('startTime', value);
};
const handleEndTimeChange = (value: string) => {
console.log("End changed:", value);
setEndTime(value);
validateField('endTime', value);
};
const handleSubmit = async () => {
if (isSubmitting) return;
// Clear previous errors
setFormErrors({});
// Validate all fields
if (!validateForm()) {
return;
}
// Final dedicated hours check
const dedicatedHoursError = validateDedicatedHours(startTime, endTime);
if (dedicatedHoursError) {
setFormErrors(prev => ({
...prev,
startTime: dedicatedHoursError
}));
return;
}
const payload = {
projectId: selectedProjectId,
date: selectedDate,
loginTime: `${startTime}:00`,
logoutTime: `${endTime}:00`,
comment
};
console.log("Submitting payload:", payload);
setIsSubmitting(true);
try {
// Add await here to properly wait for the response
const res = await window.electron.createTimesheet(payload);
console.log("API Response:", res);
if (res?.success) {
console.log("Timesheet Created Successfully");
// Show success message
alert(res.message);
// Reset form or navigate away
setSelectedProjectId("");
setSelectedDate("");
setStartTime("");
setEndTime("");
setComment("");
setFormErrors({});
setTotalAdjustment("0h 0m");
} else {
setFormErrors(prev => ({
...prev,
general: res?.message || "Failed to create timesheet"
}));
}
} catch (err: any) {
console.error("Submission error:", err);
setFormErrors(prev => ({
...prev,
general: err.message || "Something went wrong while saving timesheet"
}));
} finally {
setIsSubmitting(false);
}
};
const handleDescription = (value: string) => {
setComment(value);
};
return ( return (
<div className="max-h-screen"> <div className="max-h-screen">
<div className="w-full mx-auto"> <div className="w-full mx-auto">
{/* Header */} {/* Header */}
<div className=" "> <div className=" ">
<h1 className="text-mb font-bold mb-2">Adjust Timesheet</h1> <h1 className="text-mb font-bold mb-2">Adjust Timesheet</h1>
</div> </div>
{/* General Error Alert */}
{formErrors.general && (
<div className="alert alert-error shadow-sm my-1 px-6 py-1 text-xs text-base-100">
<span>{formErrors.general}</span>
</div>
)}
{/* Main Container */} {/* Main Container */}
<div className=" rounded-lg bg-base-100 overflow-hidden"> <div className="rounded-lg bg-base-100 overflow-hidden">
{/* Tabs Navigation */} {/* Tabs Navigation */}
<div className="flex "> <div className="flex ">
<button <button
@ -280,8 +557,7 @@ const LeaveAndAdjustments: React.FC = () => {
}`} }`}
> >
<span className="flex items-center justify-center gap-2 cursor-pointer"> <span className="flex items-center justify-center gap-2 cursor-pointer">
<svg <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg> </svg>
Create or Adjust Timesheet Create or Adjust Timesheet
@ -297,93 +573,137 @@ const LeaveAndAdjustments: React.FC = () => {
<div className="max-w-4xl "> <div className="max-w-4xl ">
<div className="space-y-3"> <div className="space-y-3">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
{/* Project Selection */}
<div className="form-control col-span-2"> <div className="form-control col-span-2">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-sm">Select project</span> <span className="label-text font-semibold text-sm">Select project</span>
</label> </label>
<select <select
className="select select-sm" className={`select select-sm ${formErrors.selectedProjectId ? 'select-error' : ''}`}
defaultValue="" value={selectedProjectId}
onChange={(e) => handleProjectSelect(e.target.value)} onChange={(e) => handleProjectSelect(e.target.value)}
> >
<option value="" disabled> <option value="" disabled>
Pick a project Pick a project
</option> </option>
{projects.map((item, index) => ( {projects.map((item, index) => (
<option key={item.projectId || index} value={item.projectId}> <option key={item.projectId || index} value={item.projectId}>
{item.projectName} {item.projectName}
</option> </option>
))} ))}
</select> </select>
{formErrors.selectedProjectId && (
{ptoErrors.fromDate && (
<label className="label"> <label className="label">
<span className="label-text-alt text-error font-medium">{ptoErrors.fromDate}</span> <span className="label-text-alt text-error font-medium">{formErrors.selectedProjectId}</span>
</label> </label>
)} )}
</div> </div>
{/* Date Selection */}
<div className="form-control col-span-2"> <div className="form-control col-span-2">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-sm">Select timesheet date</span> <span className="label-text font-semibold text-sm">Select timesheet date</span>
</label> </label>
<input <input
type="date" type="date"
value={selectedDate || ""} value={selectedDate}
onChange={(e) => handleSelectedDate(e.target.value)} onChange={(e) => handleSelectedDate(e.target.value)}
className="input input-bordered input-sm w-full focus:input-primary transition-all" className={`input input-bordered input-sm w-full focus:input-primary transition-all ${formErrors.selectedDate ? 'input-error' : ''}`}
/> />
{formErrors.selectedDate && (
<label className="label">
<span className="label-text-alt text-error font-medium">{formErrors.selectedDate}</span>
</label>
)}
</div> </div>
{/* Current Total Time */}
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-sm ">Current total time</span> <span className="label-text font-semibold text-sm ">Current total time</span>
</label> </label>
{loadingSummary ? (
<div className="skeleton h-9 w-full"></div>
) :
<input <input
type="text" type="text"
value={projectSummary ? `${projectSummary.totalWorkedHoursDecimal} hrs` : "0 hrs"} value={
projectSummary
? `${pendingOverride ? (projectSummary.dedicatedHours - Number(pendingOverride)).toFixed(2) : projectSummary.totalWorkedHoursDecimal} hrs`
: "0 hrs"
}
readOnly readOnly
className="input input-bordered input-sm w-full bg-base-200" className="input input-bordered input-sm w-full bg-base-200"
/> />
}
</div> </div>
{/* Current Pending Time */}
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-sm">Current pending time</span> <span className="label-text font-semibold text-sm">Current pending time</span>
</label> </label>
{loadingSummary ? (
<div className="skeleton h-9 w-full"></div>
) :
<input <input
type="text" type="text"
value={projectSummary ? `${projectSummary.pendingHours} hrs` : "0 hrs"} value={
projectSummary
? `${pendingOverride ?? projectSummary.pendingHours} hrs`
: "0 hrs"
}
readOnly readOnly
className="input input-bordered input-sm w-full bg-base-200" className="input input-bordered input-sm w-full bg-base-200"
/> />
}
</div> </div>
{/* Start Time */}
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-sm">Start time</span> <span className="label-text font-semibold text-sm">Start time</span>
</label> </label>
{loadingSummary ? (
<div className="skeleton h-9 w-full"></div>
) :
<input <input
type="time" type="time"
value={startTime} value={startTime}
onChange={(e) => setStartTime(e.target.value)} onChange={(e) => handleStartTimeChange(e.target.value)}
className="input input-bordered input-sm w-full focus:input-primary transition-all" className={`input input-bordered input-sm w-full focus:input-primary transition-all ${formErrors.startTime ? 'input-error' : ''}`}
/> />
}
{formErrors.startTime && (
<label className="label">
<span className="label-text-alt text-error font-medium">{formErrors.startTime}</span>
</label>
)}
</div> </div>
{/* End Time */}
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-sm">End time</span> <span className="label-text font-semibold text-sm ">End time</span>
</label> </label>
{loadingSummary ? (
<div className="skeleton h-9 w-full"></div>
) :
<input <input
type="time" type="time"
value={endTime} value={endTime}
onChange={(e) => setEndTime(e.target.value)} onChange={(e) => handleEndTimeChange(e.target.value)}
className="input input-bordered input-sm w-full focus:input-primary transition-all" className={`input input-bordered input-sm w-full focus:input-primary transition-all ${formErrors.endTime ? 'input-error' : ''}`}
/> />
}
{formErrors.endTime && (
<label className="label">
<span className="label-text-alt text-error font-medium">{formErrors.endTime}</span>
</label>
)}
</div> </div>
{/* Total Time Adjustment */}
<div className="form-control col-span-2"> <div className="form-control col-span-2">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-sm">Total time adjustment</span> <span className="label-text font-semibold text-sm">Total time adjustment</span>
@ -391,12 +711,14 @@ const LeaveAndAdjustments: React.FC = () => {
<input <input
type="text" type="text"
placeholder="Calculated automatically" placeholder="Calculated automatically"
value={totalAdjustment}
readOnly readOnly
className="input input-bordered input-sm w-full bg-base-200" className="input input-bordered input-sm w-full bg-base-200"
/> />
</div> </div>
</div> </div>
{/* Description */}
<div className="form-control flex flex-col gap-1"> <div className="form-control flex flex-col gap-1">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-sm ">Description / Reason</span> <span className="label-text font-semibold text-sm ">Description / Reason</span>
@ -404,27 +726,24 @@ const LeaveAndAdjustments: React.FC = () => {
<textarea <textarea
className="textarea text-xs textarea-bordered h-40 w-full focus:textarea-primary transition-all" className="textarea text-xs textarea-bordered h-40 w-full focus:textarea-primary transition-all"
placeholder="Please enter the task name or leave notes to help us better understand your request." placeholder="Please enter the task name or leave notes to help us better understand your request."
value={comment}
onChange={(e) => handleDescription(e.target.value)}
/> />
</div> </div>
{/* Submit Button */}
<div className="flex gap-3"> <div className="flex gap-3">
<button <button
onClick={handlePtoSubmit} onClick={handleSubmit}
className="btn btn-primary flex-1 hover:shadow-lg transition-all duration-300" disabled={isSubmitting}
className={`btn btn-primary flex-1 hover:shadow-lg transition-all duration-300 ${isSubmitting ? 'loading' : ''}`}
> >
{!isSubmitting && (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg> </svg>
Submit Request )}
</button> {isSubmitting ? 'Submitting...' : 'Submit Request'}
<button
onClick={() => {
setPtoLeave({ fromDate: '', toDate: '' });
setPtoErrors({});
}}
className="btn btn-secondary transition-all"
>
Clear
</button> </button>
</div> </div>
</div> </div>

View File

@ -2,8 +2,12 @@ import React, { useState, useEffect, useRef, FormEvent, ChangeEvent } from 'reac
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { setUserSlice } from '../store/userSlice'; import { setUserSlice } from '../store/userSlice';
import DocVaLogoLight from '../../assets/logo-colored-vertical.svg'
import DocVaLogoDark from '../../assets/logo-white-vertical.svg'
import { ThemeToggle } from '../helper/ThemeToggle';
const Login = () => { const Login = () => {
const theme = ThemeToggle();
const inputRef = useRef<HTMLFormElement | null>(null) const inputRef = useRef<HTMLFormElement | null>(null)
const navigate = useNavigate() const navigate = useNavigate()
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -13,6 +17,13 @@ const Login = () => {
const [isForgotMode, setIsForgotMode] = useState(false); const [isForgotMode, setIsForgotMode] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
const logo = theme === "dark" ? DocVaLogoDark : DocVaLogoLight;
const handleLogin = async (e: FormEvent<HTMLFormElement> | KeyboardEvent) => { const handleLogin = async (e: FormEvent<HTMLFormElement> | KeyboardEvent) => {
e.preventDefault() e.preventDefault()
@ -83,20 +94,9 @@ const Login = () => {
<div className="card-body"> <div className="card-body">
<div className="flex justify-center items-center mb-4"> <div className="flex justify-center items-center mb-4">
<div className="avatar placeholder"> <div className="avatar placeholder">
<div className="bg-primary text-primary-content rounded-full w-16 h-16 flex items-center justify-center"> {/* <div className="bg-primary text-primary-content rounded-full w-20 h-20 flex items-center justify-center"> */}
<svg <img src={logo} className='w-35 h-23' />
fill="none" {/* </div> */}
viewBox="0 0 24 24"
className="w-8 h-8 stroke-current"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
/>
</svg>
</div>
</div> </div>
</div> </div>

14
types.d.ts vendored
View File

@ -149,6 +149,20 @@ interface Window {
getProjectSummary: (projectId: string, date: string) => Promise<any>; getProjectSummary: (projectId: string, date: string) => Promise<any>;
createTimesheet: (
payload: {
projectId: string;
date: string;
loginTime: string;
logoutTime: string;
comment?: string;
}
) => Promise<{
success: boolean;
data?: any;
message?: string;
}>;
}; };
} }