merge 11/6
This commit is contained in:
parent
27c6caceb4
commit
e11d4e52f2
|
|
@ -77,6 +77,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
|
||||
"integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.24.2",
|
||||
|
|
@ -2788,6 +2789,7 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
|
|
@ -2976,7 +2978,6 @@
|
|||
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
|
||||
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"archiver-utils": "^2.1.0",
|
||||
"async": "^3.2.4",
|
||||
|
|
@ -2995,7 +2996,6 @@
|
|||
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
|
||||
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.4",
|
||||
"graceful-fs": "^4.2.0",
|
||||
|
|
@ -3017,7 +3017,6 @@
|
|||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
|
|
@ -3032,15 +3031,13 @@
|
|||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/archiver-utils/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
|
|
@ -3284,6 +3281,7 @@
|
|||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001587",
|
||||
"electron-to-chromium": "^1.4.668",
|
||||
|
|
@ -3856,7 +3854,6 @@
|
|||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
|
||||
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-crc32": "^0.2.13",
|
||||
"crc32-stream": "^4.0.2",
|
||||
|
|
@ -4038,7 +4035,6 @@
|
|||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
|
|
@ -4051,7 +4047,6 @@
|
|||
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
|
||||
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"crc-32": "^1.2.0",
|
||||
"readable-stream": "^3.4.0"
|
||||
|
|
@ -4463,6 +4458,7 @@
|
|||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.0.5.tgz",
|
||||
"integrity": "sha512-ocnZV44ZqInoSFaY54fF7BlCtw+WtbrjyPrkBhaB+Ztn7GPKjmFgRbIKytifJ8h9Cib8jdFRMgjCUtkU45Y6DA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "25.0.5",
|
||||
"builder-util": "25.0.3",
|
||||
|
|
@ -4686,7 +4682,6 @@
|
|||
"resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.0.5.tgz",
|
||||
"integrity": "sha512-N2U7LGSdt4hmEhjEeIV2XJbjj2YIrTL6enfsGKfOhGTpL6GEejUmT3gjdKUqKBS5+NBx0GWhnEwD3MpO2P6Nfg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "25.0.5",
|
||||
"archiver": "^5.3.1",
|
||||
|
|
@ -4699,7 +4694,6 @@
|
|||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
|
|
@ -4714,7 +4708,6 @@
|
|||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
|
|
@ -4727,7 +4720,6 @@
|
|||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
|
|
@ -5440,8 +5432,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
|
|
@ -6369,8 +6360,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/isbinaryfile": {
|
||||
"version": "5.0.2",
|
||||
|
|
@ -6631,7 +6621,6 @@
|
|||
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
|
||||
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readable-stream": "^2.0.5"
|
||||
},
|
||||
|
|
@ -6644,7 +6633,6 @@
|
|||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
|
|
@ -6659,15 +6647,13 @@
|
|||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lazystream/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
|
|
@ -6945,36 +6931,31 @@
|
|||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"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,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"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,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"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,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"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,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
|
|
@ -7577,7 +7558,6 @@
|
|||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -8177,8 +8157,7 @@
|
|||
"version": "2.0.1",
|
||||
"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,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
|
|
@ -8259,6 +8238,7 @@
|
|||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
|
|
@ -8270,6 +8250,7 @@
|
|||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
|
|
@ -8421,7 +8402,6 @@
|
|||
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
|
||||
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimatch": "^5.1.0"
|
||||
}
|
||||
|
|
@ -8431,7 +8411,6 @@
|
|||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
|
|
@ -9386,7 +9365,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
|
|
@ -9811,6 +9789,7 @@
|
|||
"version": "5.4.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz",
|
||||
"integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
|
|
@ -10225,7 +10204,6 @@
|
|||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
|
||||
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"archiver-utils": "^3.0.4",
|
||||
"compress-commons": "^4.1.2",
|
||||
|
|
@ -10240,7 +10218,6 @@
|
|||
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
|
||||
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.2.3",
|
||||
"graceful-fs": "^4.2.0",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import ProtectedRoute from "./routes/ProtectedRoute"
|
|||
import PublicRoute from "./routes/PublicRoute";
|
||||
import { ThemeToggle } from "./helper/ThemeToggle"
|
||||
import LeaveAndAdjustments from "./pages/LeaveAndAdjustments/LeaveAndAdjustments";
|
||||
import Timesheets from "./pages/Timesheets/Timesheets";
|
||||
import Settings from "./pages/Settings/Settings";
|
||||
import HelpSupport from "./pages/HelpSupport/HelpSupport";
|
||||
|
||||
// ✅ Define User type (customize based on your structure)
|
||||
interface User {
|
||||
|
|
@ -61,6 +64,9 @@ const App: React.FC = () => {
|
|||
<Route index element={<Dashboard />} />
|
||||
<Route path="projects" element={<Projects />} />
|
||||
<Route path="leave-adjustments" element={<LeaveAndAdjustments />} />
|
||||
<Route path="timesheet" element={<Timesheets />} />
|
||||
<Route path="settings" element={<Settings/>} />
|
||||
<Route path="help-support" element={<HelpSupport/>} />
|
||||
</Route>
|
||||
|
||||
{/* Default Redirect */}
|
||||
|
|
|
|||
|
|
@ -21,3 +21,13 @@
|
|||
/* Firefox */
|
||||
}
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -1,7 +1,24 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'
|
||||
import { Bell, Menu, LogOut, User, Settings, Home, FolderKanban, X, CalendarDays } from 'lucide-react'
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Outlet,
|
||||
NavLink,
|
||||
useNavigate,
|
||||
useLocation,
|
||||
} from "react-router-dom";
|
||||
import {
|
||||
Bell,
|
||||
Menu,
|
||||
LogOut,
|
||||
User,
|
||||
Settings,
|
||||
Home,
|
||||
FolderKanban,
|
||||
X,
|
||||
CalendarDays,
|
||||
SettingsIcon,
|
||||
TimerIcon,
|
||||
HelpCircleIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
|
|
@ -14,64 +31,68 @@ interface DashboardLayoutProps {
|
|||
}
|
||||
|
||||
const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
||||
const [isSidebarOpen, setSidebarOpen] = useState(true)
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768)
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const [isSidebarOpen, setSidebarOpen] = useState(true);
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
// ✅ Handle responsive behavior properly
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
const mobile = window.innerWidth < 768
|
||||
setIsMobile(mobile)
|
||||
setSidebarOpen(!mobile) // open on desktop, closed on mobile
|
||||
}
|
||||
const mobile = window.innerWidth < 768;
|
||||
setIsMobile(mobile);
|
||||
setSidebarOpen(!mobile);
|
||||
};
|
||||
|
||||
handleResize()
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [])
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('user')
|
||||
setUser(null)
|
||||
navigate('/login')
|
||||
}
|
||||
localStorage.removeItem("user");
|
||||
setUser(null);
|
||||
navigate("/login");
|
||||
};
|
||||
|
||||
const toggleSidebar = () => setSidebarOpen(!isSidebarOpen)
|
||||
const toggleSidebar = () => setSidebarOpen(!isSidebarOpen);
|
||||
|
||||
const closeSidebarOnMobile = () => {
|
||||
if (isMobile) setSidebarOpen(false)
|
||||
}
|
||||
if (isMobile) setSidebarOpen(false);
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ label: 'Dashboard', path: '/dashboard', icon: <Home size={18} />, exact: true },
|
||||
{ label: 'Projects', path: '/dashboard/projects', icon: <FolderKanban size={18} /> },
|
||||
{ label: 'Leave & Adjustments', path: '/dashboard/leave-adjustments', icon: <CalendarDays size={18} /> },
|
||||
]
|
||||
{ label: "Dashboard", path: "/dashboard", icon: <Home size={18} />, exact: true },
|
||||
{ label: "Projects", path: "/dashboard/projects", icon: <FolderKanban size={18} /> },
|
||||
{ label: "PTO", path: "/dashboard/leave-adjustments", icon: <CalendarDays size={18} /> },
|
||||
{ label: "Timesheet", path: "/dashboard/timesheet", icon: <TimerIcon size={18} /> },
|
||||
{ label: "Settings", path: "/dashboard/settings", icon: <SettingsIcon size={18} /> },
|
||||
{ label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-base-200 overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`
|
||||
fixed md:relative inset-y-0 left-0 z-40 transform py-1
|
||||
fixed md:relative inset-y-0 left-0 z-40 transform
|
||||
bg-base-100 border-r border-t border-base-300 transition-all duration-300 ease-in-out
|
||||
${isMobile
|
||||
${
|
||||
isMobile
|
||||
? isSidebarOpen
|
||||
? 'translate-x-0 w-64'
|
||||
: '-translate-x-full w-64'
|
||||
? "translate-x-0 w-64"
|
||||
: "-translate-x-full w-64"
|
||||
: isSidebarOpen
|
||||
? 'w-64'
|
||||
: 'w-20'}
|
||||
? "w-50"
|
||||
: "w-20"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-4 text-xl font-bold flex justify-between items-center border-b border-base-300">
|
||||
{/* Sidebar Header */}
|
||||
<div className="h-14 text-xl font-bold flex justify-between items-center border-b border-base-300">
|
||||
{isSidebarOpen ? (
|
||||
<span className="whitespace-nowrap">VA Tracker</span>
|
||||
<span className="whitespace-nowrap mx-auto">VA Tracker</span>
|
||||
) : (
|
||||
<span className="text-primary font-extrabold">VA</span>
|
||||
<span className="text-primary font-extrabold mx-auto">VA</span>
|
||||
)}
|
||||
{isMobile && (
|
||||
<button
|
||||
|
|
@ -94,8 +115,8 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 my-1 font-medium transition-all duration-150 ${
|
||||
isActive
|
||||
? 'bg-primary text-primary-content rounded-lg'
|
||||
: 'hover:bg-base-300 rounded-lg'
|
||||
? "bg-primary text-primary-content rounded-lg"
|
||||
: "hover:bg-base-300 rounded-lg"
|
||||
}`
|
||||
}
|
||||
onClick={closeSidebarOnMobile}
|
||||
|
|
@ -108,7 +129,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
</ul>
|
||||
</aside>
|
||||
|
||||
{/* Overlay for mobile */}
|
||||
{/* Mobile overlay */}
|
||||
{isMobile && isSidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/20 backdrop-blur-[1px] z-30 md:hidden"
|
||||
|
|
@ -117,14 +138,11 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<div
|
||||
className={`flex flex-col flex-1 transition-all duration-300 ease-in-out bg-base-200 ${
|
||||
!isMobile ? (isSidebarOpen ? 'md:ml-0' : 'md:ml-0') : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col flex-1 bg-base-200 transition-all duration-300 ease-in-out">
|
||||
{/* Navbar */}
|
||||
<nav className="navbar bg-base-100 border-b border-t border-base-300 p-3 sticky top-0 z-30 flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<nav className="h-14 bg-base-100 border-b border-t border-base-300 px-4 flex justify-between items-center sticky top-0 z-30">
|
||||
{/* Left */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="btn btn-ghost btn-square"
|
||||
|
|
@ -132,20 +150,25 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
>
|
||||
<Menu size={20} />
|
||||
</button>
|
||||
<span className="text-lg font-semibold ml-2 md:hidden">
|
||||
{location.pathname.includes('projects') ? 'Projects' : 'Dashboard'}
|
||||
<span className="text-lg font-semibold md:hidden">
|
||||
{location.pathname.includes("projects")
|
||||
? "Projects"
|
||||
: location.pathname.includes("leave-adjustments")
|
||||
? "Leave & Adjustments"
|
||||
: "Dashboard"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Right */}
|
||||
<div className="flex items-center gap-1">
|
||||
<button className="btn btn-ghost btn-circle">
|
||||
<Bell size={20} />
|
||||
</button>
|
||||
|
||||
<div className="dropdown dropdown-end">
|
||||
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
|
||||
<div className="w-10 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center">
|
||||
<User size={22} />
|
||||
<div className="w-8 h-8 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center">
|
||||
<User size={18} className="text-base-content" />
|
||||
</div>
|
||||
</label>
|
||||
<ul
|
||||
|
|
@ -173,7 +196,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLayout
|
||||
export default DashboardLayout;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Clock, TrendingUp, Play, Square, ProjectorIcon } from "lucide-react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react";
|
||||
|
||||
const projectsData = [
|
||||
{
|
||||
|
|
@ -153,6 +153,12 @@ interface WidgetProps {
|
|||
color?: "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info";
|
||||
}
|
||||
|
||||
interface Project {
|
||||
id: number;
|
||||
projectName: string;
|
||||
clientName: string;
|
||||
}
|
||||
|
||||
// Widget Component
|
||||
const Widget: React.FC<WidgetProps> = ({
|
||||
title,
|
||||
|
|
@ -163,7 +169,7 @@ const Widget: React.FC<WidgetProps> = ({
|
|||
color = "primary",
|
||||
}) => {
|
||||
return (
|
||||
<div className="card bg-base-100 shadow-md hover:shadow-lg transition-shadow">
|
||||
<div className="card bg-base-100 shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div className="card-body">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
|
|
@ -179,10 +185,116 @@ const Widget: React.FC<WidgetProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
|
||||
// Main Dashboard Component
|
||||
const Dashboard = () => {
|
||||
const Dashboard: React.FC = () => {
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||
const [isTracking, setIsTracking] = useState(false);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [elapsedTime, setElapsedTime] = useState(0); // in seconds
|
||||
const [startTime, setStartTime] = useState<Date | null>(null);
|
||||
const [endTime, setEndTime] = useState<Date | null>(null);
|
||||
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
// Load cached project or auto-select single project
|
||||
useEffect(() => {
|
||||
const cachedProject = localStorage.getItem("activeProject");
|
||||
if (cachedProject) {
|
||||
const parsed = JSON.parse(cachedProject) as Project;
|
||||
setSelectedProject(parsed);
|
||||
} else if (projectsData.length === 1) {
|
||||
setSelectedProject(projectsData[0]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Format elapsed time (hh:mm:ss)
|
||||
const formatTime = (seconds: number) => {
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = seconds % 60;
|
||||
return `${h}h ${m}m ${s}s`;
|
||||
};
|
||||
|
||||
// Start tracking manually
|
||||
const handleStartTracking = () => {
|
||||
if (!selectedProject) return;
|
||||
|
||||
setIsTracking(true);
|
||||
setIsPaused(false);
|
||||
setElapsedTime(0);
|
||||
setStartTime(new Date());
|
||||
setEndTime(null);
|
||||
|
||||
// Cache selected project
|
||||
localStorage.setItem("activeProject", JSON.stringify(selectedProject));
|
||||
};
|
||||
|
||||
// ⏱ Timer logic
|
||||
useEffect(() => {
|
||||
if (isTracking && !isPaused) {
|
||||
timerRef.current = setInterval(() => {
|
||||
setElapsedTime((prev) => prev + 1);
|
||||
}, 1000);
|
||||
} else if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
};
|
||||
}, [isTracking, isPaused]);
|
||||
|
||||
// ⏸ Pause/Resume button handler
|
||||
const handlePauseResume = () => {
|
||||
setIsPaused((prev) => !prev);
|
||||
};
|
||||
|
||||
// Stop tracking
|
||||
const handleStopTracking = () => {
|
||||
if (!selectedProject) return;
|
||||
|
||||
setIsTracking(false);
|
||||
setIsPaused(false);
|
||||
setEndTime(new Date());
|
||||
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
|
||||
// Keep project in view, just stop tracking.
|
||||
// Remove tracking state but keep the selected project cached.
|
||||
const summary = {
|
||||
project: selectedProject.projectName,
|
||||
totalTime: formatTime(elapsedTime),
|
||||
start: startTime?.toLocaleTimeString(),
|
||||
end: new Date().toLocaleTimeString(),
|
||||
};
|
||||
|
||||
console.log("✅ Final Tracking Summary:", summary);
|
||||
|
||||
// Save the stopped state for reference
|
||||
localStorage.setItem(
|
||||
"activeProject",
|
||||
JSON.stringify({
|
||||
...selectedProject,
|
||||
lastSession: summary,
|
||||
})
|
||||
);
|
||||
|
||||
// Reset timer values but keep selected project
|
||||
setElapsedTime(0);
|
||||
};
|
||||
|
||||
// 🧾 When user picks a project manually
|
||||
const handleProjectSelect = (id: number) => {
|
||||
if (isTracking) return;
|
||||
const project = projectsData.find((item) => item.id === id);
|
||||
if (project) {
|
||||
setSelectedProject(project);
|
||||
localStorage.setItem("activeProject", JSON.stringify(project));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="flex-1 overflow-y-auto p-4 md:px-2 md:py-0.5 no-scrollbar">
|
||||
<main className="flex-1 overflow-y-auto md:px-0.5 md:py-0 no-scrollbar">
|
||||
{/* Welcome Section */}
|
||||
<div className="mb-4">
|
||||
<h1 className="text-lg font-bold">Dashboard</h1>
|
||||
|
|
@ -198,7 +310,6 @@ const Dashboard = () => {
|
|||
|
||||
{/* Widgets Grid */}
|
||||
<div className="mb-4">
|
||||
<p className="text-md mb-3 font-bold">Overview</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3 gap-4 ">
|
||||
<Widget
|
||||
title="Logged Hours Today"
|
||||
|
|
@ -218,99 +329,143 @@ const Dashboard = () => {
|
|||
/>
|
||||
<Widget
|
||||
title="Active Project"
|
||||
value="VA Tracker Project"
|
||||
value={selectedProject ? selectedProject.projectName : "—"}
|
||||
subtitle="Until day ends"
|
||||
icon={<ProjectorIcon size={24} />}
|
||||
color="warning"
|
||||
/>
|
||||
{/* <Widget
|
||||
title="Status"
|
||||
value="Online"
|
||||
subtitle={`Active since ${formatTime(new Date(currentTime.getTime() - 6.4 * 60 * 60 * 1000))}`}
|
||||
icon={<Activity size={24} />}
|
||||
color="success"
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tracking Card */}
|
||||
<div className="w-full mb-4">
|
||||
<div className="text-md mb-2 font-bold flex justify-between">
|
||||
<div className="text-md mb-2 font-bold flex justify-between items-center">
|
||||
<span>Tracking</span>
|
||||
<div>
|
||||
<button className="ms-1 btn btn-xs btn-success btn-wide sm:w-auto rounded-full shadow-md hover:shadow-lg hover:scale-105 transition-all">
|
||||
<Play className="w-3 h-3" />
|
||||
Start Tracking
|
||||
</button>
|
||||
<button className="ms-1 btn btn-xs btn-error btn-wide sm:w-auto rounded-full shadow-md hover:shadow-lg hover:scale-105 transition-all">
|
||||
{isTracking && (
|
||||
<button
|
||||
className="btn btn-xs btn-error rounded-full"
|
||||
onClick={handleStopTracking}
|
||||
>
|
||||
<Square className="w-3 h-3" />
|
||||
Stop Tracking
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-100 shadow-md border border-base-200 transition-all duration-300 w-full">
|
||||
<div className="card bg-base-100 shadow-sm border border-base-200 transition-all duration-300 w-full">
|
||||
<div className="card-body">
|
||||
{selectedProject ? (
|
||||
<>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-center sm:text-left">
|
||||
<p className="text-sm font-extrabold text-success tracking-tight">
|
||||
0h 15m
|
||||
{formatTime(elapsedTime)}
|
||||
</p>
|
||||
<p className="text-xs text-base-content/70 mt-1">
|
||||
Current Session
|
||||
{isTracking ? "Current Session" : "Ready to Start"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-success btn-sm rounded-xl hover:scale-105 transition-transform duration-200 w-full sm:w-auto">
|
||||
Start
|
||||
{!isTracking ? (
|
||||
<button
|
||||
onClick={handleStartTracking}
|
||||
className="btn btn-sm btn-success rounded-xl hover:scale-105 transition-transform duration-200 w-full sm:w-auto"
|
||||
>
|
||||
<Play className="w-3 h-3" />
|
||||
Start Tracking
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={handlePauseResume}
|
||||
className={`btn btn-sm rounded-xl transition-transform duration-200 w-full sm:w-auto ${isPaused ? "btn-primary" : "btn-warning"
|
||||
}`}
|
||||
>
|
||||
{isPaused ? (
|
||||
<>
|
||||
<Play className="w-3 h-3" />
|
||||
Resume
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Pause className="w-3 h-3" />
|
||||
Pause
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="divider my-2"></div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 text-sm">
|
||||
{/* Project Name */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
||||
<span className="text-base-content/70">Project:</span>
|
||||
<span className="font-semibold text-base-content">
|
||||
Employee Tracker
|
||||
<span className="font-semibold text-base-content truncate">
|
||||
{selectedProject.projectName}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
{/* Client Name */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
||||
<span className="text-base-content/70">Client:</span>
|
||||
<span className="truncate">{selectedProject.clientName}</span>
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
||||
<span className="text-base-content/70">Status:</span>
|
||||
<span className="badge badge-success">Active</span>
|
||||
<span
|
||||
className={`badge ${isPaused
|
||||
? "badge-warning"
|
||||
: isTracking
|
||||
? "badge-success"
|
||||
: "badge-neutral"
|
||||
}`}
|
||||
>
|
||||
{isPaused ? "Paused" : isTracking ? "Active" : "Ready"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Start Time */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
||||
<span className="text-base-content/70">Start Time:</span>
|
||||
<span>{startTime?.toLocaleTimeString() || "—"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-base-content/60 text-center">
|
||||
Select a project to start tracking.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<p className="text-md mb-2 font-bold flex">
|
||||
<span>Projects</span>
|
||||
</p>
|
||||
{/* Projects Table */}
|
||||
<div>
|
||||
<p className="text-md mb-2 font-bold">Projects</p>
|
||||
<div
|
||||
className="overflow-y-auto no-scrollbar overflow-x-auto rounded-box border border-base-content/10 bg-base-100 shadow-md"
|
||||
style={{ minHeight: "100px", maxHeight: "250px" }}
|
||||
style={{ minHeight: "100px", maxHeight: "30vh" }}
|
||||
>
|
||||
<table className="table table-zebra w-full text-sm">
|
||||
{/* Table Head */}
|
||||
<thead className="sticky top-0 bg-base-200 z-10">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Project Name</th>
|
||||
<th>Client Name</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* Table Body */}
|
||||
<tbody>
|
||||
{projectsData.map((project) => (
|
||||
<tr key={project.id} className="align-middle">
|
||||
<th>{project.id}</th>
|
||||
<td className="whitespace-normal break-words max-w-[150px]">{project.projectName}</td>
|
||||
<td className="whitespace-normal break-words max-w-[150px]">{project.clientName}</td>
|
||||
<td>{project.projectName}</td>
|
||||
<td>{project.clientName}</td>
|
||||
<td>
|
||||
<div
|
||||
className={`badge badge-sm whitespace-nowrap ${project.status === "Active"
|
||||
|
|
@ -334,15 +489,15 @@ const Dashboard = () => {
|
|||
: "badge-primary"
|
||||
}`}
|
||||
>
|
||||
{project.action}
|
||||
</div>
|
||||
Select
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import React from "react";
|
||||
import { HelpCircle, Mail, Phone, Send, MessageSquare } from "lucide-react";
|
||||
|
||||
const HelpSupport: React.FC = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 max-h-screen">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-md font-bold flex items-center gap-2">
|
||||
Help & Support
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="card bg-base-100 border border-base-300 shadow-sm p-4">
|
||||
<h3 className="text-md font-semibold mb-2">Need Assistance?</h3>
|
||||
<p className="text-sm text-base-content/70">
|
||||
We're here to help! If you’re facing any issues, please reach out to our support team.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mt-3">
|
||||
<div className="flex items-center gap-3 border border-base-300 rounded-lg p-3 bg-base-200/40">
|
||||
<Mail size={18} className="text-primary" />
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Email</p>
|
||||
<p className="font-medium text-sm">support@vatechcorp.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 border border-base-300 rounded-lg p-3 bg-base-200/40">
|
||||
<Phone size={18} className="text-primary" />
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Phone</p>
|
||||
<p className="font-medium text-sm">+91 98765 43210</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 border border-base-300 rounded-lg p-3 bg-base-200/40">
|
||||
<MessageSquare size={18} className="text-primary" />
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Chat</p>
|
||||
<p className="font-medium text-sm">Available 9 AM - 6 PM</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelpSupport;
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import React from "react";
|
||||
|
||||
interface AdjustmentEntry {
|
||||
id: number;
|
||||
date: string;
|
||||
loggedHours: number;
|
||||
}
|
||||
|
||||
interface AdjustmentModalProps {
|
||||
editingEntry: AdjustmentEntry | null;
|
||||
newHours: number;
|
||||
setNewHours: React.Dispatch<React.SetStateAction<number>>;
|
||||
handleCloseModal: () => void;
|
||||
handleSave: () => void;
|
||||
}
|
||||
|
||||
const AdjustmentModal: React.FC<AdjustmentModalProps> = ({
|
||||
editingEntry,
|
||||
newHours,
|
||||
setNewHours,
|
||||
handleCloseModal,
|
||||
handleSave,
|
||||
}) => {
|
||||
return (
|
||||
<dialog id="editModal" className="modal modal-bottom sm:modal-middle">
|
||||
<div className="modal-box">
|
||||
<h3 className="font-bold text-md mb-4">Logged Hours</h3>
|
||||
|
||||
{editingEntry && (
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Date */}
|
||||
<div className="form-control">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<label className="label-text text-sm font-medium w-32">Date</label>
|
||||
<input
|
||||
type="text"
|
||||
value={new Date(editingEntry.date).toLocaleDateString("en-US")}
|
||||
readOnly
|
||||
className="input input-bordered input-sm bg-base-200 flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current Hours */}
|
||||
<div className="form-control">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<label className="label-text text-sm font-medium w-32">
|
||||
Current Hours
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={`${editingEntry.loggedHours} hrs`}
|
||||
readOnly
|
||||
className="input input-bordered input-sm bg-base-200 flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* New Hours */}
|
||||
<div className="form-control">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<label className="label-text text-sm font-medium w-32">
|
||||
New Hours
|
||||
</label>
|
||||
<div className="flex items-center flex-1">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="24"
|
||||
step="0.5"
|
||||
placeholder="Enter hours"
|
||||
value={newHours}
|
||||
onChange={(e) => setNewHours(Number(e.target.value))}
|
||||
className="input input-bordered input-sm flex-1 rounded-r-none"
|
||||
/>
|
||||
<span className="bg-base-200 border border-l-0 border-base-300 text-sm px-2 py-1.5 rounded-r-lg">
|
||||
hrs
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal Actions */}
|
||||
<div className="modal-action flex justify-end gap-2 mt-4">
|
||||
<button onClick={handleCloseModal} className="btn btn-sm btn-ghost">
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={handleSave} className="btn btn-sm btn-primary">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default AdjustmentModal;
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import AdjustmentModal from './AdjustmentModal';
|
||||
|
||||
interface LeaveFormData {
|
||||
fromDate: string;
|
||||
|
|
@ -12,6 +13,22 @@ interface AdjustmentEntry {
|
|||
loggedHours: number;
|
||||
}
|
||||
|
||||
interface LogEntry {
|
||||
id: number;
|
||||
date: string;
|
||||
hours: number;
|
||||
task: string;
|
||||
status: "Completed" | "In Progress" | "Pending";
|
||||
}
|
||||
|
||||
const data: LogEntry[] = [
|
||||
{ id: 1, date: "2025-11-01", hours: 8, task: "Frontend Development", status: "Completed" },
|
||||
{ id: 2, date: "2025-11-02", hours: 6, task: "API Integration", status: "In Progress" },
|
||||
{ id: 3, date: "2025-11-03", hours: 7.5, task: "UI Testing", status: "Pending" },
|
||||
{ id: 4, date: "2025-11-04", hours: 9, task: "Bug Fixing", status: "Completed" },
|
||||
{ id: 5, date: "2025-11-05", hours: 5.5, task: "Code Review", status: "Completed" },
|
||||
];
|
||||
|
||||
type TabType = 'pto' | 'sick' | 'adjustment';
|
||||
|
||||
const LeaveAndAdjustments: React.FC = () => {
|
||||
|
|
@ -122,85 +139,51 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// Adjustment Handlers
|
||||
const handlePeriodChange = (period: string) => {
|
||||
setSelectedPeriod(period);
|
||||
if (period === 'month') {
|
||||
setAdjustmentData([
|
||||
{ id: 1, date: '2025-11-01', loggedHours: 8 },
|
||||
{ id: 2, date: '2025-11-02', loggedHours: 7.5 },
|
||||
{ id: 3, date: '2025-11-03', loggedHours: 8 },
|
||||
{ id: 4, date: '2025-11-04', loggedHours: 6 },
|
||||
{ id: 5, date: '2025-11-05', loggedHours: 8 },
|
||||
]);
|
||||
} else {
|
||||
setAdjustmentData([
|
||||
{ id: 1, date: '2025-11-01', loggedHours: 8 },
|
||||
{ id: 2, date: '2025-11-02', loggedHours: 7.5 },
|
||||
{ id: 3, date: '2025-11-03', loggedHours: 8 },
|
||||
]);
|
||||
}
|
||||
};
|
||||
const [editingEntry, setEditingEntry] = useState<AdjustmentEntry | null>(null);
|
||||
const [newHours, setNewHours] = useState<number>(0);
|
||||
|
||||
const handlePeriodChange = (value: string) => setSelectedPeriod(value);
|
||||
|
||||
const handleEditClick = (entry: AdjustmentEntry) => {
|
||||
setEditingId(entry.id);
|
||||
setEditHours(entry.loggedHours.toString());
|
||||
setEditingEntry(entry);
|
||||
const modal = document.getElementById("editModal") as HTMLDialogElement;
|
||||
modal?.showModal();
|
||||
};
|
||||
|
||||
const handleSaveEdit = async (id: number) => {
|
||||
const hours = parseFloat(editHours);
|
||||
|
||||
if (isNaN(hours) || hours < 0 || hours > 24) {
|
||||
alert('Please enter valid hours (0-24)');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Updating hours for entry:', id, 'New hours:', hours);
|
||||
|
||||
setAdjustmentData(prev =>
|
||||
prev.map(entry =>
|
||||
entry.id === id ? { ...entry, loggedHours: hours } : entry
|
||||
)
|
||||
);
|
||||
|
||||
setEditingId(null);
|
||||
setEditHours('');
|
||||
alert('Hours updated successfully!');
|
||||
} catch (error) {
|
||||
alert('Failed to update hours. Please try again.');
|
||||
console.error('Error:', error);
|
||||
}
|
||||
const handleCloseModal = () => {
|
||||
setEditingEntry(null);
|
||||
setNewHours(0)
|
||||
const modal = document.getElementById("editModal") as HTMLDialogElement;
|
||||
modal?.close();
|
||||
};
|
||||
|
||||
const handleCancelEdit = () => {
|
||||
setEditingId(null);
|
||||
setEditHours('');
|
||||
const handleSave = () => {
|
||||
console.log("Saving:", { ...editingEntry, updatedHours: newHours });
|
||||
handleCloseModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="w-full mx-auto">
|
||||
{/* Header */}
|
||||
<div className=" ">
|
||||
<h1 className="text-lg font-bold mb-3">Leave & Adjustments</h1>
|
||||
<h1 className="text-mb font-bold mb-2">Leave & Adjustments</h1>
|
||||
</div>
|
||||
|
||||
{/* Main Container */}
|
||||
<div className=" shadow-lg rounded-lg bg-base-100 overflow-hidden">
|
||||
<div className=" rounded-lg bg-base-100 overflow-hidden">
|
||||
{/* Tabs Navigation */}
|
||||
<div className="flex ">
|
||||
<button
|
||||
onClick={() => setActiveTab('pto')}
|
||||
className={`flex-1 px-6 py-4 text-sm font-semibold transition-all duration-300 relative ${
|
||||
activeTab === 'pto'
|
||||
className={`flex-1 px-6 py-3 text-xs font-semibold transition-all duration-300 relative ${activeTab === 'pto'
|
||||
? ' text-primary border-b-2 border-primary'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-center gap-2 cursor-pointer">
|
||||
<svg
|
||||
className="h-5 w-5" 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" />
|
||||
</svg>
|
||||
PTO Request
|
||||
|
|
@ -208,14 +191,13 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('sick')}
|
||||
className={`flex-1 px-6 py-4 text-sm font-semibold transition-all duration-300 relative cursor-pointer ${
|
||||
activeTab === 'sick'
|
||||
className={`flex-1 px-6 py-3 text-xs font-semibold transition-all duration-300 relative cursor-pointer ${activeTab === 'sick'
|
||||
? ' text-primary border-b-2 border-primary'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-center gap-2">
|
||||
<svg className="h-5 w-5" 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="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
Sick Leave
|
||||
|
|
@ -223,14 +205,13 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('adjustment')}
|
||||
className={`flex-1 px-6 py-4 text-sm font-semibold cursor-pointer transition-all duration-300 relative ${
|
||||
activeTab === 'adjustment'
|
||||
className={`flex-1 px-6 py-3 text-xs font-semibold cursor-pointer transition-all duration-300 relative ${activeTab === 'adjustment'
|
||||
? ' text-primary border-b-2 border-primary'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-center gap-2">
|
||||
<svg className="h-5 w-5" 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="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
Adjustments
|
||||
|
|
@ -243,12 +224,12 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
{/* PTO Tab */}
|
||||
{activeTab === 'pto' && (
|
||||
<div className="animate-fade-in">
|
||||
<div className="max-w-2xl ">
|
||||
<div className="mb-3">
|
||||
<div className="max-w-4xl ">
|
||||
<div className="mb-1">
|
||||
<h2 className="text-sm font-bold mb-2">Paid Time Off Request</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
|
|
@ -292,7 +273,7 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
<textarea
|
||||
value={ptoLeave.description}
|
||||
onChange={(e) => handlePtoChange('description', e.target.value)}
|
||||
className={`textarea textarea-bordered h-32 w-full focus:textarea-primary transition-all ${ptoErrors.description ? 'textarea-error' : ''}`}
|
||||
className={`textarea textarea-bordered h-20 w-full focus:textarea-primary transition-all ${ptoErrors.description ? 'textarea-error' : ''}`}
|
||||
placeholder="Please provide a reason for your PTO request..."
|
||||
/>
|
||||
{ptoErrors.description && (
|
||||
|
|
@ -330,12 +311,12 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
{/* Sick Leave Tab */}
|
||||
{activeTab === 'sick' && (
|
||||
<div className="animate-fade-in">
|
||||
<div className="max-w-2xl ">
|
||||
<div className="mb-3">
|
||||
<div className="max-w-4xl ">
|
||||
<div className="mb-1">
|
||||
<h2 className="text-sm font-bold mb-2">Sick Leave Request</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
|
|
@ -379,7 +360,7 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
<textarea
|
||||
value={sickLeave.description}
|
||||
onChange={(e) => handleSickChange('description', e.target.value)}
|
||||
className={`textarea textarea-bordered h-32 w-full focus:textarea-primary transition-all ${sickErrors.description ? 'textarea-error' : ''}`}
|
||||
className={`textarea textarea-bordered h-20 w-full focus:textarea-primary transition-all ${sickErrors.description ? 'textarea-error' : ''}`}
|
||||
placeholder="Please describe your illness or condition..."
|
||||
/>
|
||||
{sickErrors.description && (
|
||||
|
|
@ -389,7 +370,7 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<div className="flex gap-3 ">
|
||||
<button
|
||||
onClick={handleSickSubmit}
|
||||
className="btn btn-primary flex-1 hover:shadow-lg transition-all duration-300"
|
||||
|
|
@ -404,7 +385,7 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
setSickLeave({ fromDate: '', toDate: '', description: '' });
|
||||
setSickErrors({});
|
||||
}}
|
||||
className="btn btn-ghost hover:bg-slate-100 transition-all"
|
||||
className="btn btn-secondary transition-all"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
|
|
@ -417,11 +398,10 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
{/* Adjustment Submissions Tab */}
|
||||
{activeTab === 'adjustment' && (
|
||||
<div className="animate-fade-in">
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-md font-bold mb-2">Time Adjustments</h2>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h2 className="text-md font-bold mb-2">Time Adjustments</h2>
|
||||
<div className="form-control w-48">
|
||||
<select
|
||||
value={selectedPeriod}
|
||||
|
|
@ -434,74 +414,52 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="overflow-hidden rounded-lg border border-base-content/10">
|
||||
<table className="table w-full">
|
||||
<thead className="bg-base-100">
|
||||
<tr>
|
||||
<th className=" font-bold text-base">Date</th>
|
||||
<th className=" font-bold text-base">Logged Hours</th>
|
||||
<th className=" font-bold text-base text-right">Actions</th>
|
||||
<th className="font-bold text-base">Date</th>
|
||||
<th className="font-bold text-base">Logged Hours</th>
|
||||
<th className="font-bold text-base text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{adjustmentData.map((entry) => (
|
||||
<tr key={entry.id} className=" transition-colors">
|
||||
<td className="font-medium ">
|
||||
{new Date(entry.date).toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
<tr key={entry.id} className="transition-colors">
|
||||
<td className="font-medium">
|
||||
{new Date(entry.date).toLocaleDateString("en-US", {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})}
|
||||
</td>
|
||||
<td>
|
||||
{editingId === entry.id ? (
|
||||
<input
|
||||
type="number"
|
||||
value={editHours}
|
||||
onChange={(e) => setEditHours(e.target.value)}
|
||||
className="input input-bordered input-sm w-28 focus:input-primary"
|
||||
min="0"
|
||||
max="24"
|
||||
step="0.5"
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<span className="badge badge-lg badge-ghost font-semibold">
|
||||
{entry.loggedHours} hours
|
||||
{entry.loggedHours} hrs
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
{editingId === entry.id ? (
|
||||
<div className="flex gap-2 justify-end">
|
||||
<button
|
||||
// onClick={() => handleSaveEdit(entry.id)}
|
||||
className="btn btn-success btn-sm hover:shadow-md transition-all"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCancelEdit}
|
||||
className="btn btn-ghost btn-sm hover:bg-slate-100 transition-all"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
// onClick={() => handleEditClick(entry)}
|
||||
onClick={() => handleEditClick(entry)}
|
||||
className="btn btn-primary btn-sm hover:shadow-md transition-all"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
<svg
|
||||
className="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -509,10 +467,21 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
</table>
|
||||
</div>
|
||||
|
||||
{/* No Data */}
|
||||
{adjustmentData.length === 0 && (
|
||||
<div className="text-center py-12 text-slate-500">
|
||||
<svg className="h-16 w-16 mx-auto mb-4 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
|
||||
<svg
|
||||
className="h-16 w-16 mx-auto mb-4 opacity-50"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
||||
/>
|
||||
</svg>
|
||||
<p className="font-medium">No time entries found for this period</p>
|
||||
</div>
|
||||
|
|
@ -523,6 +492,50 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* View Status */}
|
||||
<div className='mt-4'>
|
||||
<h1 className="text-md font-bold mb-2">View Status</h1>
|
||||
<div
|
||||
className="overflow-y-auto rounded-box border border-base-content/5 bg-base-100 shadow-md"
|
||||
style={{ minHeight: "150px", maxHeight: "300px" }}
|
||||
>
|
||||
<table className="table table-zebra w-full">
|
||||
{/* Table Head */}
|
||||
<thead className="sticky top-0 bg-base-200 z-10">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Date</th>
|
||||
<th>Logged Hours</th>
|
||||
<th>Task</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* Table Body */}
|
||||
<tbody>
|
||||
{data.map((log, index) => (
|
||||
<tr key={log.id}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{log.date}</td>
|
||||
<td className="font-medium">{log.hours} hrs</td>
|
||||
<td className="whitespace-normal break-words">{log.task}</td>
|
||||
<td>
|
||||
<div
|
||||
className={`badge ${log.status === "Completed"
|
||||
? "badge-success"
|
||||
: log.status === "In Progress"
|
||||
? "badge-info"
|
||||
: "badge-warning"
|
||||
}`}
|
||||
>
|
||||
{log.status}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div></div>
|
||||
<style>{`
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
|
|
@ -539,6 +552,14 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
animation: fade-in 0.3s ease-out;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<AdjustmentModal
|
||||
editingEntry={editingEntry}
|
||||
newHours={newHours}
|
||||
setNewHours={setNewHours}
|
||||
handleCloseModal={handleCloseModal}
|
||||
handleSave={handleSave}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,87 @@
|
|||
import React from 'react'
|
||||
import React from "react";
|
||||
|
||||
const Projects = () => {
|
||||
const projectsData = [
|
||||
{ id: 1, projectName: "Employee Tracker", clientName: "TechCorp Pvt Ltd", status: "Active", action: "Start Tracking" },
|
||||
{ id: 2, projectName: "Sales Dashboard", clientName: "BlueEdge Analytics", status: "Pending", action: "Start Tracking" },
|
||||
{ id: 3, projectName: "Inventory System", clientName: "LogiFlow Inc", status: "Completed", action: "View Report" },
|
||||
{ id: 4, projectName: "CRM Upgrade", clientName: "NextWave Solutions", status: "On Hold", action: "Resume" },
|
||||
{ id: 5, projectName: "Mobile App Redesign", clientName: "PixelFusion", status: "Active", action: "Start Tracking" },
|
||||
{ id: 6, projectName: "E-Commerce Backend", clientName: "ShopEasy", status: "Pending", action: "Start Tracking" },
|
||||
{ id: 7, projectName: "Finance Tracker", clientName: "MoneyWise", status: "Active", action: "Start Tracking" },
|
||||
{ id: 8, projectName: "Helpdesk Portal", clientName: "ServePro", status: "Completed", action: "View Report" },
|
||||
{ id: 9, projectName: "Marketing Automation", clientName: "AdBoost", status: "Active", action: "Start Tracking" },
|
||||
{ id: 10, projectName: "Data Pipeline", clientName: "CloudWave", status: "On Hold", action: "Resume" },
|
||||
{ id: 11, projectName: "Support Chatbot", clientName: "SmartServe", status: "Active", action: "Start Tracking" },
|
||||
{ id: 12, projectName: "Payroll System", clientName: "PeopleFirst HR", status: "Pending", action: "Start Tracking" },
|
||||
{ id: 13, projectName: "IoT Dashboard", clientName: "SensorGrid", status: "Completed", action: "View Report" },
|
||||
{ id: 14, projectName: "Security Portal", clientName: "SafeNet", status: "Active", action: "Start Tracking" },
|
||||
{ id: 15, projectName: "Social Media Tool", clientName: "Buzzify", status: "Pending", action: "Start Tracking" },
|
||||
{ id: 16, projectName: "AI Model Trainer", clientName: "NeuroTech", status: "On Hold", action: "Resume" },
|
||||
{ id: 17, projectName: "Project Tracker", clientName: "TaskFlow", status: "Active", action: "Start Tracking" },
|
||||
{ id: 18, projectName: "Booking Portal", clientName: "GoTravel", status: "Completed", action: "View Report" },
|
||||
{ id: 19, projectName: "Time Logger", clientName: "Clockify Pro", status: "Active", action: "Start Tracking" },
|
||||
{ id: 20, projectName: "Analytics Engine", clientName: "DataStream", status: "Pending", action: "Start Tracking" },
|
||||
];
|
||||
|
||||
const Projects: React.FC = () => {
|
||||
return (
|
||||
<div>Projects</div>
|
||||
)
|
||||
}
|
||||
<div className="h-full">
|
||||
<p className="text-md mb-2 font-bold flex">
|
||||
<span>Projects</span>
|
||||
</p>
|
||||
|
||||
export default Projects
|
||||
<div
|
||||
className="overflow-y-auto overflow-x-auto no-scrollbar rounded-box border border-base-content/10 bg-base-100 shadow-md"
|
||||
style={{ maxHeight: "80vh", minHeight: "200px" }} // 👈 auto scroll region
|
||||
>
|
||||
<table className="table table-zebra w-full text-sm">
|
||||
{/* Table Head */}
|
||||
<thead className="sticky top-0 bg-base-200 z-10">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Project Name</th>
|
||||
<th>Client Name</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* Table Body */}
|
||||
<tbody>
|
||||
{projectsData.map((project) => (
|
||||
<tr key={project.id} className="align-middle">
|
||||
<th>{project.id}</th>
|
||||
<td className="whitespace-normal break-words max-w-[160px]">{project.projectName}</td>
|
||||
<td className="whitespace-normal break-words max-w-[160px]">{project.clientName}</td>
|
||||
<td>
|
||||
<div
|
||||
className={`badge badge-sm ${
|
||||
project.status === "Active"
|
||||
? "badge-success"
|
||||
: project.status === "Pending"
|
||||
? "badge-warning"
|
||||
: project.status === "Completed"
|
||||
? "badge-info"
|
||||
: "badge-neutral"
|
||||
}`}
|
||||
>
|
||||
{project.status}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className={`btn btn-xs btn-primary`}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Projects;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
import React from "react";
|
||||
import { User, Mail, Lock, Edit2, Shield, CalendarDays } from "lucide-react";
|
||||
|
||||
interface UserProfile {
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
employeeId: string;
|
||||
joinDate: string;
|
||||
department: string;
|
||||
profileImage?: string;
|
||||
}
|
||||
|
||||
const userData: UserProfile = {
|
||||
name: "Jai Pandey",
|
||||
email: "jai.pandey@vatechcorp.com",
|
||||
role: "Frontend Developer",
|
||||
employeeId: "EMP-1023",
|
||||
joinDate: "2022-07-15",
|
||||
department: "Engineering",
|
||||
profileImage: "https://ui-avatars.com/api/?name=Jai+Pandey&background=random",
|
||||
};
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 max-h-screen">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-md font-bold flex items-center gap-2">
|
||||
Profile Settings
|
||||
</h2>
|
||||
<button className="btn btn-sm btn-primary gap-1">
|
||||
<Edit2 size={16} /> Edit Profile
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Profile Section */}
|
||||
<div className="card bg-base-100 border border-base-300 shadow-sm p-4 flex flex-col md:flex-row gap-4">
|
||||
{/* Profile Picture */}
|
||||
<div className="flex flex-col items-center md:w-1/3">
|
||||
<div className="avatar">
|
||||
<div className="w-28 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center">
|
||||
<User size={60} />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mt-3 text-lg font-semibold">{userData.name}</h3>
|
||||
<p className="text-sm text-base-content/70">{userData.role}</p>
|
||||
</div>
|
||||
|
||||
<div className="divider md:divider-horizontal" />
|
||||
|
||||
{/* Details Section */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 flex-1">
|
||||
<div className="p-2 rounded-md bg-base-200/50">
|
||||
<p className="text-xs text-base-content/70 flex items-center gap-2">
|
||||
<Mail size={14} /> Email
|
||||
</p>
|
||||
<p className="text-sm font-medium">{userData.email}</p>
|
||||
</div>
|
||||
|
||||
<div className="p-2 rounded-md bg-base-200/50">
|
||||
<p className="text-xs text-base-content/70 flex items-center gap-2">
|
||||
<Shield size={14} /> Role
|
||||
</p>
|
||||
<p className="text-sm font-medium">{userData.role}</p>
|
||||
</div>
|
||||
|
||||
<div className="p-2 rounded-md bg-base-200/50">
|
||||
<p className="text-xs text-base-content/70 flex items-center gap-2">
|
||||
<Lock size={14} /> Employee ID
|
||||
</p>
|
||||
<p className="text-sm font-medium">{userData.employeeId}</p>
|
||||
</div>
|
||||
|
||||
<div className="p-2 rounded-md bg-base-200/50">
|
||||
<p className="text-xs text-base-content/70 flex items-center gap-2">
|
||||
<CalendarDays size={14} /> Joining Date
|
||||
</p>
|
||||
<p className="text-sm font-medium">{userData.joinDate}</p>
|
||||
</div>
|
||||
|
||||
<div className="p-2 rounded-md bg-base-200/50 sm:col-span-2">
|
||||
<p className="text-xs text-base-content/70 flex items-center gap-2">
|
||||
<User size={14} /> Department
|
||||
</p>
|
||||
<p className="text-sm font-medium">{userData.department}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Account Settings */}
|
||||
<div className="card bg-base-100 border border-base-300 shadow-sm p-4">
|
||||
<h3 className="text-md font-semibold mb-3">Account Security</h3>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between border-t border-base-300 pt-3">
|
||||
<p className="text-sm flex items-center gap-2">
|
||||
<Lock size={16} /> Password last changed on{" "}
|
||||
<span className="font-medium text-base-content">Aug 12, 2024</span>
|
||||
</p>
|
||||
<button className="btn btn-sm btn-outline btn-primary">
|
||||
Change Password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
import React from "react";
|
||||
import { CalendarDays, Clock, FileText, CheckCircle, XCircle } from "lucide-react";
|
||||
|
||||
interface TimesheetEntry {
|
||||
id: number;
|
||||
date: string;
|
||||
project: string;
|
||||
hours: number;
|
||||
status: "Approved" | "Pending" | "Rejected";
|
||||
remarks?: string;
|
||||
}
|
||||
|
||||
const timesheetData: TimesheetEntry[] = [
|
||||
{ id: 1, date: "2025-10-28", project: "Employee Tracker", hours: 8, status: "Approved", remarks: "Full day" },
|
||||
{ id: 2, date: "2025-10-29", project: "CRM Upgrade", hours: 6, status: "Pending", remarks: "Client call delays" },
|
||||
{ id: 3, date: "2025-10-30", project: "Sales Dashboard", hours: 7, status: "Rejected", remarks: "Missing details" },
|
||||
{ id: 4, date: "2025-10-31", project: "Finance Tracker", hours: 9, status: "Approved", remarks: "Overtime" },
|
||||
{ id: 5, date: "2025-11-01", project: "AI Model Trainer", hours: 8, status: "Approved", remarks: "Normal day" },
|
||||
];
|
||||
|
||||
const Timesheets: React.FC = () => {
|
||||
const totalHours = timesheetData.reduce((acc, t) => acc + t.hours, 0);
|
||||
const approvedCount = timesheetData.filter((t) => t.status === "Approved").length;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 max-h-screen">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-md font-bold">Timesheets</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div className="card bg-base-100 border border-base-300 shadow-sm">
|
||||
<div className="card-body py-3 px-4 flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Total Logged Hours</p>
|
||||
<h3 className="text-lg font-semibold">{totalHours} hrs</h3>
|
||||
</div>
|
||||
<Clock size={20} className="text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-100 border border-base-300 shadow-sm">
|
||||
<div className="card-body py-3 px-4 flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Approved Days</p>
|
||||
<h3 className="text-lg font-semibold">{approvedCount}</h3>
|
||||
</div>
|
||||
<CheckCircle size={20} className="text-success" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-100 border border-base-300 shadow-sm">
|
||||
<div className="card-body py-3 px-4 flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Pending Days</p>
|
||||
<h3 className="text-lg font-semibold">
|
||||
{timesheetData.length - approvedCount}
|
||||
</h3>
|
||||
</div>
|
||||
<FileText size={20} className="text-warning" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timesheet Table */}
|
||||
<div className="overflow-y-auto rounded-box border border-base-content/10 bg-base-100 shadow-md max-h-[60vh]">
|
||||
<table className="table table-zebra w-full text-sm">
|
||||
<thead className="sticky top-0 bg-base-200 z-10 text-sm">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Date</th>
|
||||
<th>Project</th>
|
||||
<th>Hours</th>
|
||||
<th>Status</th>
|
||||
<th>Remarks</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{timesheetData.map((entry) => (
|
||||
<tr key={entry.id}>
|
||||
<td>{entry.id}</td>
|
||||
<td className="whitespace-nowrap">{entry.date}</td>
|
||||
<td>{entry.project}</td>
|
||||
<td>{entry.hours} hrs</td>
|
||||
<td>
|
||||
<div
|
||||
className={`badge badge-sm ${
|
||||
entry.status === "Approved"
|
||||
? "badge-success"
|
||||
: entry.status === "Pending"
|
||||
? "badge-warning"
|
||||
: "badge-error"
|
||||
}`}
|
||||
>
|
||||
{entry.status}
|
||||
</div>
|
||||
</td>
|
||||
<td>{entry.remarks || "-"}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Footer Summary */}
|
||||
<div className="flex justify-between items-center text-xs mt-2">
|
||||
<span className="text-base-content/60">
|
||||
Showing {timesheetData.length} entries
|
||||
</span>
|
||||
<span className="text-base-content/60">
|
||||
Last updated: {new Date().toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Timesheets;
|
||||
Loading…
Reference in New Issue