merge 11/6

This commit is contained in:
JaiPandey425 2025-11-06 12:29:55 +05:30
parent 27c6caceb4
commit e11d4e52f2
11 changed files with 981 additions and 328 deletions

57
package-lock.json generated
View File

@ -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",

View File

@ -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 */}

View File

@ -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;
}

View File

@ -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;

View File

@ -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>
);

View File

@ -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;

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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;

View File

@ -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;