timesheet adjustments
This commit is contained in:
parent
f0e6b704cd
commit
df92e68a0a
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>VA Tracker</title>
|
||||
<title>DocVa</title>
|
||||
<!-- <meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self';"
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
|
|
@ -2870,6 +2871,7 @@
|
|||
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
|
|
@ -3139,6 +3141,7 @@
|
|||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
|
|
@ -3341,7 +3344,6 @@
|
|||
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"archiver-utils": "^2.1.0",
|
||||
"async": "^3.2.4",
|
||||
|
|
@ -3361,7 +3363,6 @@
|
|||
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.4",
|
||||
"graceful-fs": "^4.2.0",
|
||||
|
|
@ -3383,8 +3384,7 @@
|
|||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/archiver-utils/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
|
|
@ -3392,7 +3392,6 @@
|
|||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
|
|
@ -3408,8 +3407,7 @@
|
|||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/archiver-utils/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
|
|
@ -3417,7 +3415,6 @@
|
|||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
|
|
@ -3702,6 +3699,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
|
|
@ -4319,7 +4317,6 @@
|
|||
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-crc32": "^0.2.13",
|
||||
"crc32-stream": "^4.0.2",
|
||||
|
|
@ -4521,7 +4518,6 @@
|
|||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
|
|
@ -4535,7 +4531,6 @@
|
|||
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"crc-32": "^1.2.0",
|
||||
"readable-stream": "^3.4.0"
|
||||
|
|
@ -4969,6 +4964,7 @@
|
|||
"integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "25.1.8",
|
||||
"builder-util": "25.1.7",
|
||||
|
|
@ -5188,7 +5184,6 @@
|
|||
"integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "25.1.8",
|
||||
"archiver": "^5.3.1",
|
||||
|
|
@ -5202,7 +5197,6 @@
|
|||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
|
|
@ -5218,7 +5212,6 @@
|
|||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
|
|
@ -5232,7 +5225,6 @@
|
|||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
|
|
@ -5619,16 +5611,6 @@
|
|||
"devOptional": true,
|
||||
"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": {
|
||||
"version": "1.4.5",
|
||||
"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",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
|
|
@ -7831,7 +7812,6 @@
|
|||
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readable-stream": "^2.0.5"
|
||||
},
|
||||
|
|
@ -7844,8 +7824,7 @@
|
|||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lazystream/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
|
|
@ -7853,7 +7832,6 @@
|
|||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
|
|
@ -7869,8 +7847,7 @@
|
|||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lazystream/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
|
|
@ -7878,7 +7855,6 @@
|
|||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
|
|
@ -8159,40 +8135,35 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.difference": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
|
||||
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.union": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
|
||||
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
|
|
@ -8997,7 +8968,6 @@
|
|||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -9677,8 +9647,7 @@
|
|||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
|
|
@ -9773,6 +9742,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
|
|
@ -9785,6 +9755,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
|
|
@ -9804,6 +9775,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
|
|
@ -9950,7 +9922,6 @@
|
|||
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimatch": "^5.1.0"
|
||||
}
|
||||
|
|
@ -9961,7 +9932,6 @@
|
|||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
|
|
@ -9972,7 +9942,6 @@
|
|||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
|
|
@ -10016,7 +9985,8 @@
|
|||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
|
|
@ -11293,7 +11263,6 @@
|
|||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
|
|
@ -11764,6 +11733,7 @@
|
|||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
|
|
@ -12324,7 +12294,6 @@
|
|||
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"archiver-utils": "^3.0.4",
|
||||
"compress-commons": "^4.1.2",
|
||||
|
|
@ -12340,7 +12309,6 @@
|
|||
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.2.3",
|
||||
"graceful-fs": "^4.2.0",
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
|
|
@ -226,6 +226,7 @@ ipcMain.handle("forgot-password", async (event, email: string) => {
|
|||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-running-apps', async () => {
|
||||
return await getRunningAppsList()
|
||||
});
|
||||
|
|
@ -235,7 +236,6 @@ ipcMain.handle('capture-specific-window', async (event, appTitle: string) => {
|
|||
return await captureSpecificApp(appTitle)
|
||||
});
|
||||
|
||||
|
||||
ipcMain.handle("get-profile", async () => {
|
||||
try {
|
||||
const response = await api.get("/profile");
|
||||
|
|
@ -253,7 +253,6 @@ ipcMain.handle("get-profile", async () => {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
ipcMain.handle("change-password", async (event, 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 }) => {
|
||||
try {
|
||||
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 = () => {
|
||||
|
|
@ -312,3 +334,6 @@ export const sendTokenExpired = () => {
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,20 @@ app.setAppUserModelId("com.tracker.app");
|
|||
const __filename = fileURLToPath(import.meta.url);
|
||||
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
|
||||
// ---------------------------
|
||||
|
|
@ -53,7 +67,7 @@ app.on("ready", async () => {
|
|||
show: true,
|
||||
skipTaskbar: true,
|
||||
webPreferences: {
|
||||
devTools: false,
|
||||
devTools: true,
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -62,6 +76,11 @@ app.on("ready", async () => {
|
|||
path.join(__dirname, "../assets/splash.html")
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Create the main window
|
||||
const win = new BrowserWindow({
|
||||
width: 500,
|
||||
|
|
@ -76,8 +95,9 @@ app.on("ready", async () => {
|
|||
autoHideMenuBar: true,
|
||||
frame: process.platform === "darwin" ? false : true,
|
||||
show: false, // speed improvement
|
||||
icon: resolveIcon(),
|
||||
webPreferences: {
|
||||
devTools: false,
|
||||
devTools: true,
|
||||
preload: getPreloadPath(),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
|
|
@ -101,7 +121,7 @@ app.on("ready", async () => {
|
|||
// ------------------------------------
|
||||
if (isDev()) {
|
||||
win.loadURL("http://localhost:5123");
|
||||
// win.webContents.openDevTools(); // Optional (slows startup)
|
||||
win.webContents.openDevTools(); // Optional (slows startup)
|
||||
} else {
|
||||
win.loadFile(getUIPath());
|
||||
}
|
||||
|
|
@ -128,17 +148,17 @@ app.on("ready", async () => {
|
|||
});
|
||||
|
||||
|
||||
app.on('browser-window-focus', () => {
|
||||
globalShortcut.register('CommandOrControl+R', () => {
|
||||
// Do nothing; disables reload
|
||||
});
|
||||
globalShortcut.register('F5', () => {
|
||||
// Do nothing; disables reload
|
||||
});
|
||||
globalShortcut.register('CommandOrControl+Shift+I', () => {
|
||||
// Do nothing; disables DevTools
|
||||
});
|
||||
});
|
||||
// app.on('browser-window-focus', () => {
|
||||
// globalShortcut.register('CommandOrControl+R', () => {
|
||||
// // Do nothing; disables reload
|
||||
// });
|
||||
// globalShortcut.register('F5', () => {
|
||||
// // Do nothing; disables reload
|
||||
// });
|
||||
// globalShortcut.register('CommandOrControl+Shift+I', () => {
|
||||
// // Do nothing; disables DevTools
|
||||
// });
|
||||
// });
|
||||
|
||||
app.on('browser-window-blur', () => {
|
||||
globalShortcut.unregisterAll();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||
resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height),
|
||||
getProjectList: () => ipcRenderer.invoke("get-project-list"),
|
||||
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) =>
|
||||
ipcRenderer.on("window:updated", (_, data) => callback(data)),
|
||||
|
||||
|
|
@ -45,10 +45,11 @@ contextBridge.exposeInMainWorld('electron', {
|
|||
});
|
||||
},
|
||||
|
||||
|
||||
getProjectSummary: (projectId: string, date: string) =>
|
||||
ipcRenderer.invoke("get-project-summary", { projectId, date }),
|
||||
|
||||
createTimesheet: (data: any) => ipcRenderer.invoke('create-timesheet', data),
|
||||
|
||||
} satisfies Window['electron']);
|
||||
|
||||
// function ipcInvoke<Key extends keyof EventPayloadMapping>(
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ const Dashboard: React.FC = () => {
|
|||
<tr>
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
<th>Logs Hours</th>
|
||||
{/* <th>Logs Hours</th> */}
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -282,9 +282,9 @@ const Dashboard: React.FC = () => {
|
|||
<tr key={project.projectId}>
|
||||
<td className="text-xs opacity-70">{index + 1}</td>
|
||||
<td className="font-medium">{project.projectName}</td>
|
||||
<td className="font-mono text-xs">
|
||||
{/* <td className="font-mono text-xs">
|
||||
{formatWorkTime(project.totalWorkTime)}
|
||||
</td>
|
||||
</td> */}
|
||||
<td>
|
||||
<button
|
||||
className={`btn btn-xs ${isCurrentlySelected ? "btn-success" : "btn-primary"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,15 @@ const data: LogEntry[] = [
|
|||
|
||||
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 [activeTab, setActiveTab] = useState<TabType>('create_and_adjust_timesheet');
|
||||
const projects = useSelector((state: RootState) => state.projects.projectsData);
|
||||
|
|
@ -65,6 +74,12 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
const [sickErrors, setSickErrors] = useState<Partial<LeaveFormData>>({});
|
||||
const [selectedProjectId, setSelectedProjectId] = 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
|
||||
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 errors = validateLeaveForm(ptoLeave);
|
||||
|
||||
|
|
@ -166,6 +295,8 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
|
||||
const [editingEntry, setEditingEntry] = useState<AdjustmentEntry | null>(null);
|
||||
const [newHours, setNewHours] = useState<number>(0);
|
||||
const [comment, setComment] = useState<string>("")
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handlePeriodChange = (value: string) => setSelectedPeriod(value);
|
||||
|
||||
|
|
@ -189,15 +320,16 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
|
||||
const handleProjectSelect = (projectId: string) => {
|
||||
console.log("handleProjectSelect", projectId)
|
||||
setSelectedProjectId(projectId)
|
||||
setSelectedProjectId(projectId);
|
||||
validateField('selectedProjectId', projectId);
|
||||
}
|
||||
|
||||
const handleSelectedDate = (value: string) => {
|
||||
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 localTime = utcTime.toLocal();
|
||||
|
||||
|
|
@ -206,13 +338,12 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
console.log('Local Time:', localTime.toString());
|
||||
console.log('System Timezone:', localTime.zoneName);
|
||||
console.log('Formatted Local Time:', localTime.toFormat('HH:mm'));
|
||||
|
||||
// return localTime.toFormat('HH:mm');
|
||||
};
|
||||
|
||||
const fetchLogs = async () => {
|
||||
try {
|
||||
console.log("Sending =>", selectedProjectId, selectedDate);
|
||||
setLoadingSummary(true);
|
||||
|
||||
const res = await window.electron.getProjectSummary(
|
||||
selectedProjectId,
|
||||
|
|
@ -227,13 +358,13 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
// Convert UTC times to local timezone and set them
|
||||
if (res?.data?.summary?.firstLoginTime) {
|
||||
const localStartTime = convertToLocalTime(res?.data?.summary?.firstLoginTime);
|
||||
debugTimezoneInfo(localStartTime ,"start");
|
||||
debugTimezoneInfo(localStartTime, "start");
|
||||
setStartTime(localStartTime);
|
||||
}
|
||||
|
||||
if (res?.data?.summary?.lastLogoutTime) {
|
||||
const localEndTime = convertToLocalTime(res?.data?.summary?.lastLogoutTime);
|
||||
debugTimezoneInfo(localEndTime , "end");
|
||||
debugTimezoneInfo(localEndTime, "end");
|
||||
setEndTime(localEndTime);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -247,6 +378,8 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
setProjectSummary(null);
|
||||
setStartTime("");
|
||||
setEndTime("");
|
||||
} finally {
|
||||
setLoadingSummary(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -260,16 +393,160 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
}
|
||||
}, [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 (
|
||||
<div className="max-h-screen">
|
||||
<div className="w-full mx-auto">
|
||||
|
||||
{/* Header */}
|
||||
<div className=" ">
|
||||
<h1 className="text-mb font-bold mb-2">Adjust Timesheet</h1>
|
||||
</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 */}
|
||||
<div className=" rounded-lg bg-base-100 overflow-hidden">
|
||||
<div className="rounded-lg bg-base-100 overflow-hidden">
|
||||
{/* Tabs Navigation */}
|
||||
<div className="flex ">
|
||||
<button
|
||||
|
|
@ -280,8 +557,7 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-center gap-2 cursor-pointer">
|
||||
<svg
|
||||
className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg 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" />
|
||||
</svg>
|
||||
Create or Adjust Timesheet
|
||||
|
|
@ -297,93 +573,137 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
<div className="max-w-4xl ">
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Project Selection */}
|
||||
<div className="form-control col-span-2">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold text-sm">Select project</span>
|
||||
</label>
|
||||
<select
|
||||
className="select select-sm"
|
||||
defaultValue=""
|
||||
className={`select select-sm ${formErrors.selectedProjectId ? 'select-error' : ''}`}
|
||||
value={selectedProjectId}
|
||||
onChange={(e) => handleProjectSelect(e.target.value)}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Pick a project
|
||||
</option>
|
||||
|
||||
{projects.map((item, index) => (
|
||||
<option key={item.projectId || index} value={item.projectId}>
|
||||
{item.projectName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{ptoErrors.fromDate && (
|
||||
{formErrors.selectedProjectId && (
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Date Selection */}
|
||||
<div className="form-control col-span-2">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold text-sm">Select timesheet date</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={selectedDate || ""}
|
||||
value={selectedDate}
|
||||
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>
|
||||
|
||||
{/* Current Total Time */}
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold text-sm ">Current total time</span>
|
||||
</label>
|
||||
{loadingSummary ? (
|
||||
<div className="skeleton h-9 w-full"></div>
|
||||
) :
|
||||
<input
|
||||
type="text"
|
||||
value={projectSummary ? `${projectSummary.totalWorkedHoursDecimal} hrs` : "0 hrs"}
|
||||
value={
|
||||
projectSummary
|
||||
? `${pendingOverride ? (projectSummary.dedicatedHours - Number(pendingOverride)).toFixed(2) : projectSummary.totalWorkedHoursDecimal} hrs`
|
||||
: "0 hrs"
|
||||
}
|
||||
readOnly
|
||||
className="input input-bordered input-sm w-full bg-base-200"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Current Pending Time */}
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold text-sm">Current pending time</span>
|
||||
</label>
|
||||
{loadingSummary ? (
|
||||
<div className="skeleton h-9 w-full"></div>
|
||||
) :
|
||||
<input
|
||||
type="text"
|
||||
value={projectSummary ? `${projectSummary.pendingHours} hrs` : "0 hrs"}
|
||||
value={
|
||||
projectSummary
|
||||
? `${pendingOverride ?? projectSummary.pendingHours} hrs`
|
||||
: "0 hrs"
|
||||
}
|
||||
readOnly
|
||||
className="input input-bordered input-sm w-full bg-base-200"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Start Time */}
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold text-sm">Start time</span>
|
||||
</label>
|
||||
{loadingSummary ? (
|
||||
<div className="skeleton h-9 w-full"></div>
|
||||
) :
|
||||
<input
|
||||
type="time"
|
||||
value={startTime}
|
||||
onChange={(e) => setStartTime(e.target.value)}
|
||||
className="input input-bordered input-sm w-full focus:input-primary transition-all"
|
||||
onChange={(e) => handleStartTimeChange(e.target.value)}
|
||||
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>
|
||||
|
||||
{/* End Time */}
|
||||
<div className="form-control">
|
||||
<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>
|
||||
{loadingSummary ? (
|
||||
<div className="skeleton h-9 w-full"></div>
|
||||
) :
|
||||
<input
|
||||
type="time"
|
||||
value={endTime}
|
||||
onChange={(e) => setEndTime(e.target.value)}
|
||||
className="input input-bordered input-sm w-full focus:input-primary transition-all"
|
||||
onChange={(e) => handleEndTimeChange(e.target.value)}
|
||||
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>
|
||||
|
||||
{/* Total Time Adjustment */}
|
||||
<div className="form-control col-span-2">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold text-sm">Total time adjustment</span>
|
||||
|
|
@ -391,12 +711,14 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
<input
|
||||
type="text"
|
||||
placeholder="Calculated automatically"
|
||||
value={totalAdjustment}
|
||||
readOnly
|
||||
className="input input-bordered input-sm w-full bg-base-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="form-control flex flex-col gap-1">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold text-sm ">Description / Reason</span>
|
||||
|
|
@ -404,27 +726,24 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
<textarea
|
||||
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."
|
||||
value={comment}
|
||||
onChange={(e) => handleDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={handlePtoSubmit}
|
||||
className="btn btn-primary flex-1 hover:shadow-lg transition-all duration-300"
|
||||
onClick={handleSubmit}
|
||||
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">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
Submit Request
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setPtoLeave({ fromDate: '', toDate: '' });
|
||||
setPtoErrors({});
|
||||
}}
|
||||
className="btn btn-secondary transition-all"
|
||||
>
|
||||
Clear
|
||||
)}
|
||||
{isSubmitting ? 'Submitting...' : 'Submit Request'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@ import React, { useState, useEffect, useRef, FormEvent, ChangeEvent } from 'reac
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
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 theme = ThemeToggle();
|
||||
const inputRef = useRef<HTMLFormElement | null>(null)
|
||||
const navigate = useNavigate()
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -13,6 +17,13 @@ const Login = () => {
|
|||
const [isForgotMode, setIsForgotMode] = 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) => {
|
||||
e.preventDefault()
|
||||
|
|
@ -83,20 +94,9 @@ const Login = () => {
|
|||
<div className="card-body">
|
||||
<div className="flex justify-center items-center mb-4">
|
||||
<div className="avatar placeholder">
|
||||
<div className="bg-primary text-primary-content rounded-full w-16 h-16 flex items-center justify-center">
|
||||
<svg
|
||||
fill="none"
|
||||
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 className="bg-primary text-primary-content rounded-full w-20 h-20 flex items-center justify-center"> */}
|
||||
<img src={logo} className='w-35 h-23' />
|
||||
{/* </div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -149,6 +149,20 @@ interface Window {
|
|||
|
||||
|
||||
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;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue