leave and adjustments
This commit is contained in:
parent
d33c603e6c
commit
6040fa238d
|
|
@ -16,6 +16,8 @@ app.on('ready', async () => {
|
|||
// disables default system frame (dont do this if you want a proper working menu bar)
|
||||
frame: process.platform === 'darwin' ? false : true,
|
||||
});
|
||||
|
||||
|
||||
if (isDev()) {
|
||||
mainWindow.loadURL('http://localhost:5123');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,25 +1,40 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom'
|
||||
import Login from './pages/Login'
|
||||
import Dashboard from './pages/Dashboard'
|
||||
import Projects from './pages/Projects/Projects'
|
||||
import DashboardLayout from './layout/DashboardLayout'
|
||||
import ProtectedRoute from './routes/ProtectedRoute'
|
||||
import PublicRoute from './routes/PublicRoute'
|
||||
import { ThemeToggle } from './helper/ThemeToggle'
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
|
||||
import Login from "./pages/Login";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import Projects from "./pages/Projects/Projects";
|
||||
import DashboardLayout from "./layout/DashboardLayout";
|
||||
import ProtectedRoute from "./routes/ProtectedRoute"
|
||||
import PublicRoute from "./routes/PublicRoute";
|
||||
import { ThemeToggle } from "./helper/ThemeToggle"
|
||||
import LeaveAndAdjustments from "./pages/LeaveAndAdjustments/LeaveAndAdjustments";
|
||||
|
||||
const App = () => {
|
||||
const theme = ThemeToggle()
|
||||
const [user, setUser] = useState(null)
|
||||
// ✅ Define User type (customize based on your structure)
|
||||
interface User {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const App: React.FC = () => {
|
||||
const theme = ThemeToggle();
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
// ✅ Apply theme
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute('data-theme', theme)
|
||||
}, [theme])
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
}, [theme]);
|
||||
|
||||
// ✅ Load user from localStorage
|
||||
useEffect(() => {
|
||||
const storedUser = localStorage.getItem('user')
|
||||
if (storedUser) setUser(JSON.parse(storedUser))
|
||||
}, [])
|
||||
const storedUser = localStorage.getItem("user");
|
||||
if (storedUser) {
|
||||
try {
|
||||
setUser(JSON.parse(storedUser));
|
||||
} catch (err) {
|
||||
console.error("Invalid user in localStorage", err);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
|
|
@ -45,13 +60,17 @@ const App = () => {
|
|||
>
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="projects" element={<Projects />} />
|
||||
<Route path="leave-adjustments" element={<LeaveAndAdjustments />} />
|
||||
</Route>
|
||||
|
||||
{/* Default Redirect */}
|
||||
<Route path="*" element={<Navigate to={user ? '/dashboard' : '/login'} />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<Navigate to={user ? "/dashboard" : "/login"} replace />}
|
||||
/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const ThemeToggle = () => {
|
|||
setTheme(darkQuery.matches ? "dark" : "light");
|
||||
|
||||
// Listen for changes
|
||||
const handleChange = (e) => {
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
setTheme(e.matches ? "dark" : "light");
|
||||
};
|
||||
|
||||
|
|
@ -1,8 +1,19 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'
|
||||
import { Bell, Menu, LogOut, User, Settings, Home, FolderKanban, X } from 'lucide-react'
|
||||
import { Bell, Menu, LogOut, User, Settings, Home, FolderKanban, X, CalendarDays } from 'lucide-react'
|
||||
|
||||
const DashboardLayout = ({ user, setUser }) => {
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface DashboardLayoutProps {
|
||||
user: User | null;
|
||||
setUser: React.Dispatch<React.SetStateAction<User | null>>;
|
||||
}
|
||||
|
||||
const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
||||
const [isSidebarOpen, setSidebarOpen] = useState(true)
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768)
|
||||
const navigate = useNavigate()
|
||||
|
|
@ -36,6 +47,7 @@ const DashboardLayout = ({ user, setUser }) => {
|
|||
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} /> },
|
||||
]
|
||||
|
||||
return (
|
||||
|
|
@ -44,7 +56,7 @@ const DashboardLayout = ({ user, setUser }) => {
|
|||
<aside
|
||||
className={`
|
||||
fixed md:relative inset-y-0 left-0 z-40 transform py-1
|
||||
bg-base-100 border-r border-base-300 transition-all duration-300 ease-in-out
|
||||
bg-base-100 border-r border-t border-base-300 transition-all duration-300 ease-in-out
|
||||
${isMobile
|
||||
? isSidebarOpen
|
||||
? 'translate-x-0 w-64'
|
||||
|
|
@ -111,7 +123,7 @@ const DashboardLayout = ({ user, setUser }) => {
|
|||
}`}
|
||||
>
|
||||
{/* Navbar */}
|
||||
<nav className="navbar bg-base-100 border-b border-base-300 p-3 sticky top-0 z-30 flex justify-between items-center">
|
||||
<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">
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
|
|
@ -156,7 +168,7 @@ const DashboardLayout = ({ user, setUser }) => {
|
|||
</nav>
|
||||
|
||||
{/* Page Content */}
|
||||
<main className="flex-1 overflow-y-auto p-4 md:p-6 no-scrollbar">
|
||||
<main className="flex-1 overflow-y-auto p-4 md:px-6 no-scrollbar">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
|
@ -1,530 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import {
|
||||
Menu,
|
||||
X,
|
||||
Clock,
|
||||
Activity,
|
||||
TrendingUp,
|
||||
Calendar,
|
||||
User,
|
||||
LogOut,
|
||||
Settings,
|
||||
Bell,
|
||||
Search,
|
||||
Play,
|
||||
Square,
|
||||
ProjectorIcon,
|
||||
Projector,
|
||||
ToolCase,
|
||||
History,
|
||||
HelpCircle,
|
||||
CalendarDays
|
||||
} from 'lucide-react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
|
||||
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'
|
||||
}
|
||||
]
|
||||
|
||||
// Sidebar Component
|
||||
|
||||
|
||||
// Navbar Component
|
||||
const Navbar = ({ toggleSidebar, user }) => {
|
||||
return (
|
||||
<>
|
||||
{/* Navbar */}
|
||||
<div className="navbar bg-base-100 px-4 sticky top-0 z-30 border-b border-base-300 py-4 ">
|
||||
{/* Left side */}
|
||||
<div className="flex-1 gap-2">
|
||||
<button onClick={toggleSidebar} className="btn btn-ghost btn-square lg:hidden">
|
||||
<Menu size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Right side */}
|
||||
<div className="flex-none gap-2">
|
||||
{/* User Dropdown */}
|
||||
<div className="dropdown dropdown-end mx-3">
|
||||
<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>
|
||||
</label>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="mt-3 p-2 shadow-lg menu menu-sm dropdown-content bg-base-200 rounded-box w-64"
|
||||
>
|
||||
<li className="menu-title">
|
||||
<div className="flex items-center gap-3 p-2">
|
||||
<div className="avatar online">
|
||||
<div className="w-12 rounded-full flex justify-center items-center">
|
||||
<User size={25} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold">{user.name}</p>
|
||||
<p className="text-xs text-base-content/60">{user.email}</p>
|
||||
<div className="badge badge-success badge-xs mt-1">Online</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<div className="divider my-1"></div>
|
||||
<li>
|
||||
<a>
|
||||
<Settings size={16} /> Settings
|
||||
</a>
|
||||
</li>
|
||||
<div className="divider my-1"></div>
|
||||
<li>
|
||||
<a className="text-error">
|
||||
<LogOut size={16} /> Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// Widget Component
|
||||
const Widget = ({ title, value, subtitle, icon, trend, color = 'primary' }) => {
|
||||
return (
|
||||
<div className="card bg-base-100 shadow-md hover:shadow-2xl transition-shadow">
|
||||
<div className="card-body">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-medium text-base-content/60">{title}</h3>
|
||||
<p className="text-3xl font-bold mt-2">{value}</p>
|
||||
</div>
|
||||
<div className={`p-3 bg-${color}/10 rounded-lg`}>
|
||||
<div className={`text-${color}`}>{icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Activity Timeline Component
|
||||
const ActivityTimeline = () => {
|
||||
const activities = [
|
||||
{ time: '09:00 AM', status: 'online', label: 'Started working' },
|
||||
{ time: '10:30 AM', status: 'productive', label: 'Deep work session' },
|
||||
{ time: '12:00 PM', status: 'idle', label: 'Break time' },
|
||||
{ time: '01:00 PM', status: 'online', label: 'Resumed work' },
|
||||
{ time: '03:30 PM', status: 'productive', label: 'Meeting completed' }
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="card bg-base-100 shadow-xl">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">Today's Activity</h2>
|
||||
<div className="space-y-4 mt-4">
|
||||
{activities.map((activity, index) => (
|
||||
<div key={index} className="flex items-start gap-4">
|
||||
<div className="flex flex-col items-center">
|
||||
<div
|
||||
className={`w-3 h-3 rounded-full ${
|
||||
activity.status === 'online'
|
||||
? 'bg-success'
|
||||
: activity.status === 'productive'
|
||||
? 'bg-primary'
|
||||
: 'bg-warning'
|
||||
}`}
|
||||
/>
|
||||
{index < activities.length - 1 && <div className="w-0.5 h-12 bg-base-300 my-1" />}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{activity.label}</p>
|
||||
<p className="text-xs text-base-content/60">{activity.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Progress Component
|
||||
const ProductivityProgress = () => {
|
||||
return (
|
||||
<div className="card bg-base-100 shadow-xl">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">Productivity Breakdown</h2>
|
||||
<div className="space-y-4 mt-4">
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>Productive Time</span>
|
||||
<span className="font-semibold">75%</span>
|
||||
</div>
|
||||
<progress className="progress progress-success" value="75" max="100"></progress>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>Idle Time</span>
|
||||
<span className="font-semibold">15%</span>
|
||||
</div>
|
||||
<progress className="progress progress-warning" value="15" max="100"></progress>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>Break Time</span>
|
||||
<span className="font-semibold">10%</span>
|
||||
</div>
|
||||
<progress className="progress progress-info" value="10" max="100"></progress>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="stats stats-vertical shadow mt-4">
|
||||
<div className="stat py-3">
|
||||
<div className="stat-title text-xs">Efficiency Score</div>
|
||||
<div className="stat-value text-2xl text-success">92%</div>
|
||||
<div className="stat-desc">Above team average</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Main Dashboard Component
|
||||
const Dashboard = () => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
const [theme, setTheme] = useState('light')
|
||||
const [currentTime, setCurrentTime] = useState(new Date())
|
||||
|
||||
const user = {
|
||||
name: 'John Doe',
|
||||
email: 'john@company.com',
|
||||
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=John',
|
||||
status: 'online'
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
const systemTheme = darkModeQuery.matches ? 'dark' : 'light'
|
||||
setTheme(systemTheme)
|
||||
document.documentElement.setAttribute('data-theme', systemTheme)
|
||||
|
||||
const handleThemeChange = (e) => {
|
||||
const newTheme = e.matches ? 'dark' : 'light'
|
||||
setTheme(newTheme)
|
||||
document.documentElement.setAttribute('data-theme', newTheme)
|
||||
}
|
||||
|
||||
darkModeQuery.addEventListener('change', handleThemeChange)
|
||||
|
||||
// Update time every minute
|
||||
const timer = setInterval(() => setCurrentTime(new Date()), 60000)
|
||||
|
||||
return () => {
|
||||
darkModeQuery.removeEventListener('change', handleThemeChange)
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const toggleSidebar = () => setSidebarOpen(!sidebarOpen)
|
||||
|
||||
const formatTime = (date) => {
|
||||
return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4 md:p-6 no-scrollbar">
|
||||
{/* Welcome Section */}
|
||||
<div className="mb-4">
|
||||
<h1 className="text-3xl font-bold">Dashboard</h1>
|
||||
</div>
|
||||
|
||||
{/* Widgets Grid */}
|
||||
<div className="mb-4">
|
||||
<p className="text-2xl 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"
|
||||
value="6h 24m"
|
||||
subtitle="Today's total time"
|
||||
icon={<Clock size={24} />}
|
||||
trend={12}
|
||||
color="primary"
|
||||
/>
|
||||
<Widget
|
||||
title="Logged Hours This Week"
|
||||
value="4h 48m"
|
||||
subtitle="75% productivity"
|
||||
icon={<TrendingUp size={24} />}
|
||||
trend={8}
|
||||
color="success"
|
||||
/>
|
||||
<Widget
|
||||
title="Active Project"
|
||||
value="VA Tracker Project"
|
||||
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>
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="text-2xl mb-3 font-bold flex justify-between">
|
||||
<span>Projects</span>
|
||||
<div>
|
||||
<button className="ms-2 btn btn-sm btn-success btn-wide sm:w-auto rounded-full shadow-md hover:shadow-lg hover:scale-105 transition-all">
|
||||
<Play className="w-5 h-5" />
|
||||
Start Tracking
|
||||
</button>
|
||||
<button className="ms-2 btn btn-sm btn-error btn-wide sm:w-auto rounded-full shadow-md hover:shadow-lg hover:scale-105 transition-all">
|
||||
<Square className="w-5 h-5" />
|
||||
Stop Tracking
|
||||
</button>
|
||||
</div>
|
||||
</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' }}
|
||||
>
|
||||
<table className="table">
|
||||
{/* 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}>
|
||||
<th>{project.id}</th>
|
||||
<td>{project.projectName}</td>
|
||||
<td>{project.clientName}</td>
|
||||
<td>
|
||||
<div
|
||||
className={`badge ${
|
||||
project.status === 'Active'
|
||||
? 'badge-success'
|
||||
: project.status === 'Pending'
|
||||
? 'badge-warning'
|
||||
: project.status === 'Completed'
|
||||
? 'badge-info'
|
||||
: 'badge-neutral'
|
||||
}`}
|
||||
>
|
||||
{project.status}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
className={`badge ${
|
||||
project.action === 'View Report'
|
||||
? 'badge-info'
|
||||
: project.action === 'Resume'
|
||||
? 'badge-secondary'
|
||||
: 'badge-primary'
|
||||
}`}
|
||||
>
|
||||
{project.action}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<p className="text-xl font-bold mb-4">Tracking</p>
|
||||
|
||||
<div className="card bg-base-100 shadow-md border border-base-200 transition-all duration-300 w-full">
|
||||
<div className="card-body">
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-center sm:text-left">
|
||||
<p className="text-5xl font-extrabold text-success tracking-tight">0h 15m</p>
|
||||
<p className="text-sm text-base-content/70 mt-1">Current Session</p>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-success btn-lg rounded-xl hover:scale-105 transition-transform duration-200 w-full sm:w-auto">
|
||||
Start
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="divider my-4"></div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-base-content/70">Project:</span>
|
||||
<span className="font-semibold text-base-content">Employee Tracker</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<span className="text-base-content/70">Status:</span>
|
||||
<span className="badge badge-success">Active</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
|
|
@ -0,0 +1,343 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Clock, TrendingUp, Play, Square, ProjectorIcon } from "lucide-react";
|
||||
|
||||
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",
|
||||
},
|
||||
];
|
||||
|
||||
interface WidgetProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
subtitle?: string;
|
||||
icon?: React.ReactNode;
|
||||
trend?: number;
|
||||
color?: "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info";
|
||||
}
|
||||
|
||||
// Widget Component
|
||||
const Widget: React.FC<WidgetProps> = ({
|
||||
title,
|
||||
value,
|
||||
subtitle,
|
||||
icon,
|
||||
trend,
|
||||
color = "primary",
|
||||
}) => {
|
||||
return (
|
||||
<div className="card bg-base-100 shadow-md hover:shadow-lg transition-shadow">
|
||||
<div className="card-body">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className=" font-medium text-base-content/60">{title}</h3>
|
||||
<p className="text-sm font-bold mt-2">{value}</p>
|
||||
</div>
|
||||
<div className={`p-3 bg-${color}/10 rounded-lg`}>
|
||||
<div className={`text-${color}`}>{icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Main Dashboard Component
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<main className="flex-1 overflow-y-auto p-4 md:px-2 md:py-0.5 no-scrollbar">
|
||||
{/* Welcome Section */}
|
||||
<div className="mb-4">
|
||||
<h1 className="text-lg font-bold">Dashboard</h1>
|
||||
</div>
|
||||
|
||||
{/* 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"
|
||||
value="6h 24m"
|
||||
subtitle="Today's total time"
|
||||
icon={<Clock size={24} />}
|
||||
trend={12}
|
||||
color="primary"
|
||||
/>
|
||||
<Widget
|
||||
title="Logged Hours This Week"
|
||||
value="4h 48m"
|
||||
subtitle="75% productivity"
|
||||
icon={<TrendingUp size={24} />}
|
||||
trend={8}
|
||||
color="success"
|
||||
/>
|
||||
<Widget
|
||||
title="Active Project"
|
||||
value="VA Tracker Project"
|
||||
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>
|
||||
|
||||
<div className="w-full mb-4">
|
||||
<div className="text-md mb-2 font-bold flex justify-between">
|
||||
<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">
|
||||
<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-body">
|
||||
<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
|
||||
</p>
|
||||
<p className="text-xs text-base-content/70 mt-1">
|
||||
Current Session
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-success btn-sm rounded-xl hover:scale-105 transition-transform duration-200 w-full sm:w-auto">
|
||||
Start
|
||||
</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">
|
||||
<span className="text-base-content/70">Project:</span>
|
||||
<span className="font-semibold text-base-content">
|
||||
Employee Tracker
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<span className="text-base-content/70">Status:</span>
|
||||
<span className="badge badge-success">Active</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<p className="text-md mb-2 font-bold flex">
|
||||
<span>Projects</span>
|
||||
</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" }}
|
||||
>
|
||||
<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>
|
||||
<div
|
||||
className={`badge badge-sm whitespace-nowrap ${project.status === "Active"
|
||||
? "badge-success"
|
||||
: project.status === "Pending"
|
||||
? "badge-warning"
|
||||
: project.status === "Completed"
|
||||
? "badge-info"
|
||||
: "badge-neutral"
|
||||
}`}
|
||||
>
|
||||
{project.status}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
className={`badge badge-sm whitespace-nowrap ${project.action === "View Report"
|
||||
? "badge-info"
|
||||
: project.action === "Resume"
|
||||
? "badge-secondary"
|
||||
: "badge-primary"
|
||||
}`}
|
||||
>
|
||||
{project.action}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
|
@ -0,0 +1,546 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
interface LeaveFormData {
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface AdjustmentEntry {
|
||||
id: number;
|
||||
date: string;
|
||||
loggedHours: number;
|
||||
}
|
||||
|
||||
type TabType = 'pto' | 'sick' | 'adjustment';
|
||||
|
||||
const LeaveAndAdjustments: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<TabType>('pto');
|
||||
|
||||
// PTO State
|
||||
const [ptoLeave, setPtoLeave] = useState<LeaveFormData>({
|
||||
fromDate: '',
|
||||
toDate: '',
|
||||
description: ''
|
||||
});
|
||||
const [ptoErrors, setPtoErrors] = useState<Partial<LeaveFormData>>({});
|
||||
|
||||
// Sick Leave State
|
||||
const [sickLeave, setSickLeave] = useState<LeaveFormData>({
|
||||
fromDate: '',
|
||||
toDate: '',
|
||||
description: ''
|
||||
});
|
||||
const [sickErrors, setSickErrors] = useState<Partial<LeaveFormData>>({});
|
||||
|
||||
// Adjustment State
|
||||
const [selectedPeriod, setSelectedPeriod] = useState<string>('week');
|
||||
const [adjustmentData, setAdjustmentData] = useState<AdjustmentEntry[]>([
|
||||
{ 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 [editingId, setEditingId] = useState<number | null>(null);
|
||||
const [editHours, setEditHours] = useState<string>('');
|
||||
|
||||
// Validation function
|
||||
const validateLeaveForm = (data: LeaveFormData): Partial<LeaveFormData> => {
|
||||
const errors: Partial<LeaveFormData> = {};
|
||||
|
||||
if (!data.fromDate) {
|
||||
errors.fromDate = 'From date is required';
|
||||
}
|
||||
|
||||
if (!data.toDate) {
|
||||
errors.toDate = 'To date is required';
|
||||
}
|
||||
|
||||
if (data.fromDate && data.toDate && new Date(data.fromDate) > new Date(data.toDate)) {
|
||||
errors.toDate = 'To date must be after from date';
|
||||
}
|
||||
|
||||
if (!data.description.trim()) {
|
||||
errors.description = 'Description/Reason is required';
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
// PTO Handlers
|
||||
const handlePtoChange = (field: keyof LeaveFormData, value: string) => {
|
||||
setPtoLeave(prev => ({ ...prev, [field]: value }));
|
||||
if (ptoErrors[field]) {
|
||||
setPtoErrors(prev => ({ ...prev, [field]: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
const handlePtoSubmit = async () => {
|
||||
const errors = validateLeaveForm(ptoLeave);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setPtoErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Submitting PTO:', ptoLeave);
|
||||
alert('PTO request submitted successfully!');
|
||||
|
||||
setPtoLeave({ fromDate: '', toDate: '', description: '' });
|
||||
setPtoErrors({});
|
||||
} catch (error) {
|
||||
alert('Failed to submit request. Please try again.');
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Sick Leave Handlers
|
||||
const handleSickChange = (field: keyof LeaveFormData, value: string) => {
|
||||
setSickLeave(prev => ({ ...prev, [field]: value }));
|
||||
if (sickErrors[field]) {
|
||||
setSickErrors(prev => ({ ...prev, [field]: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSickSubmit = async () => {
|
||||
const errors = validateLeaveForm(sickLeave);
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setSickErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Submitting Sick Leave:', sickLeave);
|
||||
alert('Sick Leave request submitted successfully!');
|
||||
|
||||
setSickLeave({ fromDate: '', toDate: '', description: '' });
|
||||
setSickErrors({});
|
||||
} catch (error) {
|
||||
alert('Failed to submit request. Please try again.');
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 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 handleEditClick = (entry: AdjustmentEntry) => {
|
||||
setEditingId(entry.id);
|
||||
setEditHours(entry.loggedHours.toString());
|
||||
};
|
||||
|
||||
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 handleCancelEdit = () => {
|
||||
setEditingId(null);
|
||||
setEditHours('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className=" ">
|
||||
<h1 className="text-lg font-bold mb-3">Leave & Adjustments</h1>
|
||||
</div>
|
||||
|
||||
{/* Main Container */}
|
||||
<div className=" shadow-lg 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'
|
||||
? ' 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">
|
||||
<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
|
||||
</span>
|
||||
</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'
|
||||
? ' 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">
|
||||
<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
|
||||
</span>
|
||||
</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'
|
||||
? ' 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">
|
||||
<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
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="px-8 py-4">
|
||||
{/* PTO Tab */}
|
||||
{activeTab === 'pto' && (
|
||||
<div className="animate-fade-in">
|
||||
<div className="max-w-2xl ">
|
||||
<div className="mb-3">
|
||||
<h2 className="text-sm font-bold mb-2">Paid Time Off Request</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold ">From Date</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={ptoLeave.fromDate}
|
||||
onChange={(e) => handlePtoChange('fromDate', e.target.value)}
|
||||
className={`input input-bordered w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`}
|
||||
/>
|
||||
{ptoErrors.fromDate && (
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-error font-medium">{ptoErrors.fromDate}</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold">To Date</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={ptoLeave.toDate}
|
||||
onChange={(e) => handlePtoChange('toDate', e.target.value)}
|
||||
className={`input input-bordered w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`}
|
||||
/>
|
||||
{ptoErrors.toDate && (
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-error font-medium">{ptoErrors.toDate}</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-control flex flex-col gap-1">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold ">Description / Reason</span>
|
||||
</label>
|
||||
<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' : ''}`}
|
||||
placeholder="Please provide a reason for your PTO request..."
|
||||
/>
|
||||
{ptoErrors.description && (
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-error font-medium">{ptoErrors.description}</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={handlePtoSubmit}
|
||||
className="btn btn-primary flex-1 hover:shadow-lg transition-all duration-300"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
Submit Request
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setPtoLeave({ fromDate: '', toDate: '', description: '' });
|
||||
setPtoErrors({});
|
||||
}}
|
||||
className="btn btn-secondary transition-all"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sick Leave Tab */}
|
||||
{activeTab === 'sick' && (
|
||||
<div className="animate-fade-in">
|
||||
<div className="max-w-2xl ">
|
||||
<div className="mb-3">
|
||||
<h2 className="text-sm font-bold mb-2">Sick Leave Request</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold ">From Date</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={sickLeave.fromDate}
|
||||
onChange={(e) => handleSickChange('fromDate', e.target.value)}
|
||||
className={`input input-bordered w-full focus:input-primary transition-all ${sickErrors.fromDate ? 'input-error' : ''}`}
|
||||
/>
|
||||
{sickErrors.fromDate && (
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-error font-medium">{sickErrors.fromDate}</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold ">To Date</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={sickLeave.toDate}
|
||||
onChange={(e) => handleSickChange('toDate', e.target.value)}
|
||||
className={`input input-bordered w-full focus:input-primary transition-all ${sickErrors.toDate ? 'input-error' : ''}`}
|
||||
/>
|
||||
{sickErrors.toDate && (
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-error font-medium">{sickErrors.toDate}</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-control flex flex-col gap-1">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold ">Description / Reason</span>
|
||||
</label>
|
||||
<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' : ''}`}
|
||||
placeholder="Please describe your illness or condition..."
|
||||
/>
|
||||
{sickErrors.description && (
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-error font-medium">{sickErrors.description}</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<button
|
||||
onClick={handleSickSubmit}
|
||||
className="btn btn-primary flex-1 hover:shadow-lg transition-all duration-300"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
Submit Request
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSickLeave({ fromDate: '', toDate: '', description: '' });
|
||||
setSickErrors({});
|
||||
}}
|
||||
className="btn btn-ghost hover:bg-slate-100 transition-all"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 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>
|
||||
|
||||
<div className="form-control w-48">
|
||||
<select
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => handlePeriodChange(e.target.value)}
|
||||
className="select select-bordered focus:select-primary font-semibold"
|
||||
>
|
||||
<option value="week">This Week</option>
|
||||
<option value="month">This Month</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</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'
|
||||
})}
|
||||
</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
|
||||
</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)}
|
||||
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>
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{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>
|
||||
<p className="font-medium">No time entries found for this period</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.3s ease-out;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeaveAndAdjustments;
|
||||
|
|
@ -1,35 +1,42 @@
|
|||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import React, { useState, useEffect, useRef, FormEvent, ChangeEvent } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
const Login = ({ setUser }) => {
|
||||
const [formData, setFormData] = useState({ email: 'jai@gmail.com', password: '12345' })
|
||||
const inputRef = useRef(null)
|
||||
interface User {
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
interface LoginProps {
|
||||
setUser: (user: User) => void
|
||||
}
|
||||
|
||||
const Login: React.FC<LoginProps> = ({ setUser }) => {
|
||||
const [formData, setFormData] = useState<User>({ email: 'jai@gmail.com', password: '12345' })
|
||||
const inputRef = useRef<HTMLFormElement | null>(null)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleChange = (e) => {
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value })
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement> | KeyboardEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
const { email, password } = formData
|
||||
|
||||
if (email === 'jai@gmail.com' && password === '12345') {
|
||||
// alert('✅ Login successful!')
|
||||
console.log('Login successful:', formData)
|
||||
// Redirect or further logic here
|
||||
localStorage.setItem("user", JSON.stringify(formData));
|
||||
setUser(formData);
|
||||
localStorage.setItem('user', JSON.stringify(formData))
|
||||
setUser(formData)
|
||||
navigate('/')
|
||||
} else {
|
||||
alert('❌ Invalid email or password!')
|
||||
alert('Invalid email or password!')
|
||||
}
|
||||
}
|
||||
|
||||
// ⌨️ Detect Enter key press
|
||||
// Detect Enter key press
|
||||
useEffect(() => {
|
||||
const handleEnter = (e) => {
|
||||
const handleEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSubmit(e)
|
||||
}
|
||||
|
|
@ -43,15 +50,12 @@ const Login = ({ setUser }) => {
|
|||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-primary/10 via-base-200 to-secondary/10 p-4">
|
||||
{/* Login Card */}
|
||||
<div className="card w-full max-w-md shadow-2xl bg-base-100 backdrop-blur-sm">
|
||||
<div className="card-body">
|
||||
{/* Avatar */}
|
||||
<div className="flex justify-center items-center mb-4">
|
||||
<div className="avatar placeholder">
|
||||
<div className="bg-primary text-primary-content rounded-full w-16 h-16 flex items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
className="w-8 h-8 stroke-current"
|
||||
|
|
@ -67,24 +71,20 @@ const Login = ({ setUser }) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h2 className="text-center text-3xl font-bold mb-2">Welcome Back</h2>
|
||||
<p className="text-center text-base-content/60 mb-6">Sign in to Employee Tracker</p>
|
||||
|
||||
{/* Form */}
|
||||
<form
|
||||
ref={inputRef}
|
||||
className="flex flex-col items-center justify-center w-full"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{/* Email */}
|
||||
<div className="form-control w-90 px-5">
|
||||
<label className="label">
|
||||
<span className="label-text font-medium">Email Address</span>
|
||||
</label>
|
||||
<label className="input input-bordered flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
className="w-4 h-4 opacity-70"
|
||||
|
|
@ -104,14 +104,12 @@ const Login = ({ setUser }) => {
|
|||
</label>
|
||||
</div>
|
||||
|
||||
{/* Password */}
|
||||
<div className="form-control w-90 px-5 mt-4">
|
||||
<label className="label">
|
||||
<span className="label-text font-medium">Password</span>
|
||||
</label>
|
||||
<label className="input input-bordered flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
className="w-4 h-4 opacity-70"
|
||||
|
|
@ -134,7 +132,6 @@ const Login = ({ setUser }) => {
|
|||
</label>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="form-control w-full px-5 mt-6">
|
||||
<button type="submit" className="btn btn-primary w-full">
|
||||
Sign In
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// src/routes/ProtectedRoute.jsx
|
||||
import React from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
|
||||
const ProtectedRoute = ({ user, children }) => {
|
||||
if (!user) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import React from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
user: object | null;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ user, children }) => {
|
||||
if (!user) return <Navigate to="/login" replace />;
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// src/routes/PublicRoute.jsx
|
||||
import React from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
|
||||
const PublicRoute = ({ user, children }) => {
|
||||
if (user) {
|
||||
return <Navigate to="/dashboard" replace />;
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
export default PublicRoute;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import React from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
|
||||
interface PublicRouteProps {
|
||||
user: object | null;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PublicRoute: React.FC<PublicRouteProps> = ({ user, children }) => {
|
||||
if (user) return <Navigate to="/dashboard" replace />;
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default PublicRoute;
|
||||
Loading…
Reference in New Issue