leave and adjustments

This commit is contained in:
JaiPandey425 2025-11-05 15:48:29 +05:30
parent d33c603e6c
commit 6040fa238d
13 changed files with 997 additions and 604 deletions

View File

@ -16,6 +16,8 @@ app.on('ready', async () => {
// disables default system frame (dont do this if you want a proper working menu bar) // disables default system frame (dont do this if you want a proper working menu bar)
frame: process.platform === 'darwin' ? false : true, frame: process.platform === 'darwin' ? false : true,
}); });
if (isDev()) { if (isDev()) {
mainWindow.loadURL('http://localhost:5123'); mainWindow.loadURL('http://localhost:5123');
} else { } else {

View File

@ -1,25 +1,40 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from "react";
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom' import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
import Login from './pages/Login' import Login from "./pages/Login";
import Dashboard from './pages/Dashboard' import Dashboard from "./pages/Dashboard";
import Projects from './pages/Projects/Projects' import Projects from "./pages/Projects/Projects";
import DashboardLayout from './layout/DashboardLayout' import DashboardLayout from "./layout/DashboardLayout";
import ProtectedRoute from './routes/ProtectedRoute' import ProtectedRoute from "./routes/ProtectedRoute"
import PublicRoute from './routes/PublicRoute' import PublicRoute from "./routes/PublicRoute";
import { ThemeToggle } from './helper/ThemeToggle' import { ThemeToggle } from "./helper/ThemeToggle"
import LeaveAndAdjustments from "./pages/LeaveAndAdjustments/LeaveAndAdjustments";
const App = () => { // ✅ Define User type (customize based on your structure)
const theme = ThemeToggle() interface User {
const [user, setUser] = useState(null) email: string;
password: string;
}
const App: React.FC = () => {
const theme = ThemeToggle();
const [user, setUser] = useState<User | null>(null);
// ✅ Apply theme
useEffect(() => { useEffect(() => {
document.documentElement.setAttribute('data-theme', theme) document.documentElement.setAttribute("data-theme", theme);
}, [theme]) }, [theme]);
// ✅ Load user from localStorage
useEffect(() => { useEffect(() => {
const storedUser = localStorage.getItem('user') const storedUser = localStorage.getItem("user");
if (storedUser) setUser(JSON.parse(storedUser)) if (storedUser) {
}, []) try {
setUser(JSON.parse(storedUser));
} catch (err) {
console.error("Invalid user in localStorage", err);
}
}
}, []);
return ( return (
<BrowserRouter> <BrowserRouter>
@ -45,13 +60,17 @@ const App = () => {
> >
<Route index element={<Dashboard />} /> <Route index element={<Dashboard />} />
<Route path="projects" element={<Projects />} /> <Route path="projects" element={<Projects />} />
<Route path="leave-adjustments" element={<LeaveAndAdjustments />} />
</Route> </Route>
{/* Default Redirect */} {/* Default Redirect */}
<Route path="*" element={<Navigate to={user ? '/dashboard' : '/login'} />} /> <Route
path="*"
element={<Navigate to={user ? "/dashboard" : "/login"} replace />}
/>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
) );
} };
export default App export default App;

View File

@ -11,7 +11,7 @@ export const ThemeToggle = () => {
setTheme(darkQuery.matches ? "dark" : "light"); setTheme(darkQuery.matches ? "dark" : "light");
// Listen for changes // Listen for changes
const handleChange = (e) => { const handleChange = (e: MediaQueryListEvent) => {
setTheme(e.matches ? "dark" : "light"); setTheme(e.matches ? "dark" : "light");
}; };

View File

@ -1,8 +1,19 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom' 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 [isSidebarOpen, setSidebarOpen] = useState(true)
const [isMobile, setIsMobile] = useState(window.innerWidth < 768) const [isMobile, setIsMobile] = useState(window.innerWidth < 768)
const navigate = useNavigate() const navigate = useNavigate()
@ -36,6 +47,7 @@ const DashboardLayout = ({ user, setUser }) => {
const menuItems = [ const menuItems = [
{ label: 'Dashboard', path: '/dashboard', icon: <Home size={18} />, exact: true }, { label: 'Dashboard', path: '/dashboard', icon: <Home size={18} />, exact: true },
{ label: 'Projects', path: '/dashboard/projects', icon: <FolderKanban size={18} /> }, { label: 'Projects', path: '/dashboard/projects', icon: <FolderKanban size={18} /> },
{ label: 'Leave & Adjustments', path: '/dashboard/leave-adjustments', icon: <CalendarDays size={18} /> },
] ]
return ( return (
@ -44,7 +56,7 @@ const DashboardLayout = ({ user, setUser }) => {
<aside <aside
className={` 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 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 ${isMobile
? isSidebarOpen ? isSidebarOpen
? 'translate-x-0 w-64' ? 'translate-x-0 w-64'
@ -111,7 +123,7 @@ const DashboardLayout = ({ user, setUser }) => {
}`} }`}
> >
{/* Navbar */} {/* 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"> <div className="flex items-center">
<button <button
onClick={toggleSidebar} onClick={toggleSidebar}
@ -156,7 +168,7 @@ const DashboardLayout = ({ user, setUser }) => {
</nav> </nav>
{/* Page Content */} {/* 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 /> <Outlet />
</main> </main>
</div> </div>

View File

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

343
src/ui/pages/Dashboard.tsx Normal file
View File

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

View File

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

View File

@ -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' import { useNavigate } from 'react-router-dom'
const Login = ({ setUser }) => { interface User {
const [formData, setFormData] = useState({ email: 'jai@gmail.com', password: '12345' }) email: string
const inputRef = useRef(null) 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 navigate = useNavigate()
const handleChange = (e) => { const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value }) setFormData({ ...formData, [e.target.name]: e.target.value })
} }
const handleSubmit = (e) => { const handleSubmit = (e: FormEvent<HTMLFormElement> | KeyboardEvent) => {
e.preventDefault() e.preventDefault()
const { email, password } = formData const { email, password } = formData
if (email === 'jai@gmail.com' && password === '12345') { if (email === 'jai@gmail.com' && password === '12345') {
// alert('✅ Login successful!')
console.log('Login successful:', formData) console.log('Login successful:', formData)
// Redirect or further logic here localStorage.setItem('user', JSON.stringify(formData))
localStorage.setItem("user", JSON.stringify(formData)); setUser(formData)
setUser(formData);
navigate('/') navigate('/')
} else { } else {
alert('❌ Invalid email or password!') alert('Invalid email or password!')
} }
} }
// ⌨️ Detect Enter key press // Detect Enter key press
useEffect(() => { useEffect(() => {
const handleEnter = (e) => { const handleEnter = (e: KeyboardEvent) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
handleSubmit(e) handleSubmit(e)
} }
@ -43,15 +50,12 @@ const Login = ({ setUser }) => {
return ( 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"> <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 w-full max-w-md shadow-2xl bg-base-100 backdrop-blur-sm">
<div className="card-body"> <div className="card-body">
{/* Avatar */}
<div className="flex justify-center items-center mb-4"> <div className="flex justify-center items-center mb-4">
<div className="avatar placeholder"> <div className="avatar placeholder">
<div className="bg-primary text-primary-content rounded-full w-16 h-16 flex items-center justify-center"> <div className="bg-primary text-primary-content rounded-full w-16 h-16 flex items-center justify-center">
<svg <svg
xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
className="w-8 h-8 stroke-current" className="w-8 h-8 stroke-current"
@ -67,24 +71,20 @@ const Login = ({ setUser }) => {
</div> </div>
</div> </div>
{/* Title */}
<h2 className="text-center text-3xl font-bold mb-2">Welcome Back</h2> <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> <p className="text-center text-base-content/60 mb-6">Sign in to Employee Tracker</p>
{/* Form */}
<form <form
ref={inputRef} ref={inputRef}
className="flex flex-col items-center justify-center w-full" className="flex flex-col items-center justify-center w-full"
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
{/* Email */}
<div className="form-control w-90 px-5"> <div className="form-control w-90 px-5">
<label className="label"> <label className="label">
<span className="label-text font-medium">Email Address</span> <span className="label-text font-medium">Email Address</span>
</label> </label>
<label className="input input-bordered flex items-center gap-2"> <label className="input input-bordered flex items-center gap-2">
<svg <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="currentColor" fill="currentColor"
className="w-4 h-4 opacity-70" className="w-4 h-4 opacity-70"
@ -104,14 +104,12 @@ const Login = ({ setUser }) => {
</label> </label>
</div> </div>
{/* Password */}
<div className="form-control w-90 px-5 mt-4"> <div className="form-control w-90 px-5 mt-4">
<label className="label"> <label className="label">
<span className="label-text font-medium">Password</span> <span className="label-text font-medium">Password</span>
</label> </label>
<label className="input input-bordered flex items-center gap-2"> <label className="input input-bordered flex items-center gap-2">
<svg <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="currentColor" fill="currentColor"
className="w-4 h-4 opacity-70" className="w-4 h-4 opacity-70"
@ -134,7 +132,6 @@ const Login = ({ setUser }) => {
</label> </label>
</div> </div>
{/* Submit Button */}
<div className="form-control w-full px-5 mt-6"> <div className="form-control w-full px-5 mt-6">
<button type="submit" className="btn btn-primary w-full"> <button type="submit" className="btn btn-primary w-full">
Sign In Sign In

View File

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

View File

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

View File

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

View File

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