sumona-banerjeee c5946ce95e first commit for the leave management system. built a Leave Management System with role-based access:
Employees can apply for leave and track balance, status, and history.
Managers can view all requests and approve/reject them.
Login with separate dashboards for employees and managers with the dummy json.
Tables with filters (All, Pending, Approved, Rejected) for easy management.
2026-04-29 11:22:14 +05:30

136 lines
6.7 KiB
TypeScript

"use client";
import { useState, useEffect, useCallback } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/context/AuthContext";
import {
getLeavesForUser,
getUserBalance,
LeaveRequest,
} from "@/lib/leavesStore";
import { LeaveSheet } from "@/components/LeaveSheet";
import { LeaveTable } from "@/components/LeaveTable";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
CalendarDays,
PlusCircle,
Clock,
CheckCircle2,
XCircle,
Wallet,
} from "lucide-react";
export default function EmployeePage() {
const { user, logout, isLoading } = useAuth();
const router = useRouter();
const [leaves, setLeaves] = useState<LeaveRequest[]>([]);
const [balance, setBalance] = useState(0);
const [sheetOpen, setSheetOpen] = useState(false);
const [dataLoaded, setDataLoaded] = useState(false);
const loadData = useCallback(async () => {
if (!user) return;
const [data, bal] = await Promise.all([
getLeavesForUser(user.id),
Promise.resolve(getUserBalance(user.id)),
]);
setLeaves(data.sort((a, b) => b.appliedAt.localeCompare(a.appliedAt)));
setBalance(bal);
setDataLoaded(true);
}, [user]);
useEffect(() => {
if (!isLoading && !user) router.replace("/");
else if (!isLoading && user?.role === "manager") router.replace("/manager");
else if (!isLoading && user) loadData();
}, [user, isLoading, router, loadData]);
if (isLoading || !user || !dataLoaded) {
return (
<div className="min-h-screen flex items-center justify-center" style={{ background: "var(--surface-base)" }}>
<div className="flex flex-col items-center gap-3">
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: "linear-gradient(135deg, var(--brand-600), var(--brand-400))" }}>
<CalendarDays className="w-5 h-5" style={{ color: "var(--text-on-brand)" }} />
</div>
<p className="text-sm font-medium" style={{ color: "var(--text-muted)" }}>Loading</p>
</div>
</div>
);
}
const pending = leaves.filter((l) => l.status === "Pending").length;
const approved = leaves.filter((l) => l.status === "Approved").length;
const rejected = leaves.filter((l) => l.status === "Rejected").length;
const stats = [
{ label: "Leave Balance", value: balance, unit: "days", icon: Wallet, color: "var(--brand-600)", bg: "var(--brand-50)", border: "var(--brand-200)" },
{ label: "Pending", value: pending, icon: Clock, color: "var(--color-pending)", bg: "var(--color-pending-bg)", border: "var(--color-pending-border)" },
{ label: "Approved", value: approved, icon: CheckCircle2, color: "var(--color-success)", bg: "var(--color-success-bg)", border: "var(--color-success-border)" },
{ label: "Rejected", value: rejected, icon: XCircle, color: "var(--color-danger)", bg: "var(--color-danger-bg)", border: "var(--color-danger-border)" },
];
return (
<div className="min-h-screen" style={{ background: "var(--surface-base)" }}>
<header style={{ background: "var(--surface-card)", borderBottom: "1px solid var(--surface-border)", position: "sticky", top: 0, zIndex: 40 }}>
<div className="max-w-5xl mx-auto px-4 py-3.5 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl flex items-center justify-center" style={{ background: "linear-gradient(135deg, var(--brand-600), var(--brand-400))", boxShadow: "0 4px 10px -2px rgb(99 102 241 / 0.3)" }}>
<CalendarDays className="w-4.5 h-4.5" style={{ color: "var(--text-on-brand)" }} />
</div>
<div><span className="font-bold text-base" style={{ color: "var(--text-primary)" }}>LeaveFlow</span></div>
</div>
<div className="flex items-center gap-3">
<div className="hidden sm:flex items-center gap-2">
<span className="text-sm font-medium" style={{ color: "var(--text-secondary)" }}>{user.name}</span>
</div>
<Button size="sm" onClick={logout} className="text-xs font-semibold px-4" style={{ background: "linear-gradient(135deg, var(--brand-600), var(--brand-500))", color: "var(--text-on-brand)", border: "none", borderRadius: "var(--radius-sm)" }}>Logout</Button>
</div>
</div>
</header>
<main className="max-w-5xl mx-auto px-4 py-8 space-y-8">
<div className="flex items-start justify-between flex-wrap gap-4">
<div>
<h1 className="text-2xl font-bold" style={{ color: "var(--text-primary)" }}>Hello, {user.name.split(" ")[0]}</h1>
<p className="text-sm mt-1" style={{ color: "var(--text-muted)" }}>Apply for leave from here</p>
</div>
<Button onClick={() => setSheetOpen(true)} className="gap-2 font-semibold" style={{ background: "linear-gradient(135deg, var(--brand-600), var(--brand-500))", border: "none", borderRadius: "var(--radius-md)", boxShadow: "0 4px 12px -2px rgb(99 102 241 / 0.35)" }}>
<PlusCircle className="w-4 h-4" />Apply Leave
</Button>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
{stats.map((s) => {
const Icon = s.icon;
return (
<Card key={s.label} style={{ border: `1px solid ${s.border}`, background: s.bg, borderRadius: "var(--radius-md)", boxShadow: "none" }}>
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-2">
<Icon className="w-4 h-4" style={{ color: s.color }} />
<span className="text-xs font-medium" style={{ color: s.color }}>{s.label}</span>
</div>
<p className="text-2xl font-bold" style={{ color: s.color }}>
{s.value}{s.unit && <span className="text-sm font-normal ml-1">{s.unit}</span>}
</p>
</CardContent>
</Card>
);
})}
</div>
<Card style={{ border: "1px solid var(--surface-border)", borderRadius: "var(--radius-lg)", background: "var(--surface-card)", boxShadow: "var(--shadow-card)" }}>
<CardHeader className="pb-0" style={{ borderBottom: "1px solid var(--surface-border)", paddingBottom: "1rem", marginBottom: 0 }}>
<CardTitle className="text-base font-semibold" style={{ color: "var(--text-primary)" }}>Leave History</CardTitle>
</CardHeader>
<CardContent className="p-0">
<LeaveTable leaves={leaves} />
</CardContent>
</Card>
</main>
<LeaveSheet open={sheetOpen} onOpenChange={setSheetOpen} userId={user.id} leaveBalance={balance} onSuccess={loadData} />
</div>
);
}