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.
189 lines
7.4 KiB
TypeScript
189 lines
7.4 KiB
TypeScript
"use client";
|
|
|
|
import { LeaveRequest, daysBetween } from "@/lib/leavesStore";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { Button } from "@/components/ui/button";
|
|
import { CheckCircle2, XCircle, Clock, CalendarRange } from "lucide-react";
|
|
|
|
interface LeaveTableProps {
|
|
leaves: LeaveRequest[];
|
|
isManager?: boolean;
|
|
onApprove?: (id: number) => void;
|
|
onReject?: (id: number) => void;
|
|
loadingId?: number | null;
|
|
}
|
|
|
|
const STATUS_CONFIG = {
|
|
Approved: {
|
|
className: "status-approved",
|
|
icon: CheckCircle2,
|
|
},
|
|
Pending: {
|
|
className: "status-pending",
|
|
icon: Clock,
|
|
},
|
|
Rejected: {
|
|
className: "status-rejected",
|
|
icon: XCircle,
|
|
},
|
|
};
|
|
|
|
const formatDate = (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
const day = date.getDate();
|
|
const month = date.toLocaleString("en-US", { month: "short" });
|
|
const year = date.getFullYear();
|
|
return `${day} ${month}, ${year}`;
|
|
};
|
|
|
|
export function LeaveTable({ leaves, isManager, onApprove, onReject, loadingId }: LeaveTableProps) {
|
|
if (leaves.length === 0) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center py-16 gap-2">
|
|
<CalendarRange className="w-10 h-10" style={{ color: "var(--text-muted)" }} />
|
|
<p className="font-medium" style={{ color: "var(--text-secondary)" }}>
|
|
No leave requests yet
|
|
</p>
|
|
<p className="text-sm" style={{ color: "var(--text-muted)" }}>
|
|
{isManager ? "No requests to review" : "Apply for your first leave"}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="overflow-x-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow style={{ borderBottom: "1px solid var(--surface-border)" }}>
|
|
{isManager && (
|
|
<TableHead style={{ color: "var(--text-muted)", fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
Employee
|
|
</TableHead>
|
|
)}
|
|
<TableHead style={{ color: "var(--text-muted)", fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
Type
|
|
</TableHead>
|
|
<TableHead style={{ color: "var(--text-muted)", fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
Duration
|
|
</TableHead>
|
|
<TableHead style={{ color: "var(--text-muted)", fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
Days
|
|
</TableHead>
|
|
<TableHead style={{ color: "var(--text-muted)", fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
Reason
|
|
</TableHead>
|
|
<TableHead style={{ color: "var(--text-muted)", fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
Status
|
|
</TableHead>
|
|
{isManager && (
|
|
<TableHead style={{ color: "var(--text-muted)", fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
Action
|
|
</TableHead>
|
|
)}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{leaves.map((leave) => {
|
|
const config = STATUS_CONFIG[leave.status];
|
|
const StatusIcon = config.icon;
|
|
const days = daysBetween(leave.fromDate, leave.toDate);
|
|
const isLoading = loadingId === leave.id;
|
|
|
|
return (
|
|
<TableRow
|
|
key={leave.id}
|
|
style={{ borderBottom: "1px solid var(--surface-border)" }}
|
|
className="hover:bg-[var(--surface-hover)] transition-colors"
|
|
>
|
|
{isManager && (
|
|
<TableCell>
|
|
<span className="text-sm font-medium" style={{ color: "var(--text-primary)" }}>
|
|
{leave.userName}
|
|
</span>
|
|
</TableCell>
|
|
)}
|
|
<TableCell>
|
|
<span className="text-sm font-medium" style={{ color: "var(--text-primary)" }}>
|
|
{leave.type}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm" style={{ color: "var(--text-secondary)", fontFamily: "DM Mono, monospace", fontSize: "0.8rem" }}>
|
|
{formatDate(leave.fromDate)}-{formatDate(leave.toDate)}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span
|
|
className="inline-flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold"
|
|
style={{ background: "var(--brand-50)", color: "var(--brand-700)" }}
|
|
>
|
|
{days}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm max-w-[180px] truncate block" style={{ color: "var(--text-secondary)" }}>
|
|
{leave.reason}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span
|
|
className={`inline-flex items-center gap-1.5 text-xs font-semibold px-2.5 py-1 rounded-full ${config.className}`}
|
|
>
|
|
<StatusIcon className="w-3 h-3" />
|
|
{leave.status}
|
|
</span>
|
|
</TableCell>
|
|
{isManager && (
|
|
<TableCell>
|
|
{leave.status === "Pending" ? (
|
|
<div className="flex items-center gap-1.5">
|
|
<Button
|
|
size="sm"
|
|
onClick={() => onApprove?.(leave.id)}
|
|
disabled={isLoading}
|
|
className="h-7 text-xs font-semibold"
|
|
style={{
|
|
background: "var(--color-success-bg)",
|
|
color: "var(--color-success)",
|
|
border: "1px solid var(--color-success-border)",
|
|
borderRadius: "var(--radius-sm)",
|
|
}}
|
|
>
|
|
{isLoading ? "…" : <><CheckCircle2 className="w-3 h-3 mr-1" />Approve</>}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
onClick={() => onReject?.(leave.id)}
|
|
disabled={isLoading}
|
|
className="h-7 text-xs font-semibold"
|
|
style={{
|
|
background: "var(--color-danger-bg)",
|
|
color: "var(--color-danger)",
|
|
border: "1px solid var(--color-danger-border)",
|
|
borderRadius: "var(--radius-sm)",
|
|
}}
|
|
>
|
|
<XCircle className="w-3 h-3 mr-1" />Reject
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<span className="text-xs" style={{ color: "var(--text-muted)" }}>—</span>
|
|
)}
|
|
</TableCell>
|
|
)}
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
);
|
|
} |