184 lines
5.5 KiB
TypeScript
184 lines
5.5 KiB
TypeScript
import React from "react";
|
|
import { Loader2, RefreshCw } from "lucide-react";
|
|
import { DateTime } from "luxon";
|
|
|
|
interface LeaveItem {
|
|
leaveId: string;
|
|
dateFrom: string;
|
|
dateTo: string;
|
|
totalDays: number;
|
|
leaveType: string;
|
|
comment: string;
|
|
status: string;
|
|
appliedOn: string;
|
|
reviewedAt: string | null;
|
|
rejectionReason: string | null;
|
|
}
|
|
|
|
interface LeaveTableProps {
|
|
isTracking: boolean;
|
|
leaves: LeaveItem[];
|
|
loading: boolean;
|
|
lastRefreshed: Date | null;
|
|
onRefresh: () => Promise<void>;
|
|
}
|
|
|
|
const LeaveTable: React.FC<LeaveTableProps> = ({
|
|
isTracking,
|
|
leaves,
|
|
loading,
|
|
lastRefreshed,
|
|
onRefresh
|
|
}) => {
|
|
const [refreshing, setRefreshing] = React.useState(false);
|
|
|
|
// 🌍 Convert UTC → Local System Time
|
|
const toLocal = (utcString: string, format: string) => {
|
|
return DateTime.fromISO(utcString, { zone: "utc" })
|
|
.setZone(DateTime.local().zoneName)
|
|
.toFormat(format);
|
|
};
|
|
|
|
const formatDate = (d: string) => toLocal(d, "yyyy-MM-dd");
|
|
const formatDateTime = (d: string) => toLocal(d, "yyyy-MM-dd HH:mm:ss");
|
|
|
|
const handleRefresh = async () => {
|
|
setRefreshing(true);
|
|
await onRefresh();
|
|
setRefreshing(false);
|
|
};
|
|
|
|
const renderSkeletonRows = () =>
|
|
Array.from({ length: 5 }).map((_, index) => (
|
|
<tr key={`skeleton-${index}`} className="hover:bg-base-200">
|
|
<td><div className="skeleton h-4 w-6 bg-base-300"></div></td>
|
|
<td><div className="skeleton h-4 w-20 bg-base-300"></div></td>
|
|
<td><div className="skeleton h-4 w-28 bg-base-300"></div></td>
|
|
<td><div className="skeleton h-4 w-16 bg-base-300"></div></td>
|
|
<td><div className="skeleton h-4 w-10 bg-base-300"></div></td>
|
|
<td><div className="skeleton h-4 w-24 bg-base-300"></div></td>
|
|
</tr>
|
|
));
|
|
|
|
const formatLastRefreshed = () =>
|
|
lastRefreshed
|
|
? DateTime.fromJSDate(lastRefreshed)
|
|
.setZone(DateTime.local().zoneName)
|
|
.toFormat("HH:mm:ss")
|
|
: "Never";
|
|
|
|
return (
|
|
<>
|
|
{/* Table Container */}
|
|
<div
|
|
className="flex-1 overflow-y-auto rounded-box border border-base-content/10 bg-base-100 shadow-md no-scrollbar"
|
|
style={{
|
|
height: isTracking ? "calc(100vh - 200px)" : "calc(100vh - 160px)",
|
|
}}
|
|
>
|
|
<table className="table table-zebra w-full text-[11px]">
|
|
<thead className="sticky top-0 bg-base-200 z-10 text-[12px]">
|
|
<tr className="text-center">
|
|
<th>#</th>
|
|
<th>Leave Type</th>
|
|
<th>Date From → To</th>
|
|
<th>Status</th>
|
|
<th>Total Days</th>
|
|
<th>Applied On</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody className="text-center">
|
|
{loading ? (
|
|
renderSkeletonRows()
|
|
) : refreshing && leaves.length === 0 ? (
|
|
renderSkeletonRows()
|
|
) : leaves.length > 0 ? (
|
|
leaves.map((leave, index) => (
|
|
<tr key={leave.leaveId}>
|
|
<td>{index + 1}</td>
|
|
|
|
<td>
|
|
<span className="badge badge-xs badge-primary">
|
|
{leave.leaveType}
|
|
</span>
|
|
</td>
|
|
|
|
<td className="whitespace-nowrap">
|
|
<span className="">{formatDate(leave.dateFrom)} </span>
|
|
→ {" "}
|
|
<span className="">{formatDate(leave.dateTo)}</span>
|
|
</td>
|
|
|
|
<td>
|
|
<span
|
|
className={`badge badge-xs ${
|
|
leave.status === "PENDING"
|
|
? "badge-warning"
|
|
: leave.status === "APPROVED"
|
|
? "badge-success"
|
|
: "badge-error"
|
|
}`}
|
|
>
|
|
{leave.status}
|
|
</span>
|
|
</td>
|
|
|
|
<td>{leave.totalDays}</td>
|
|
|
|
<td className="whitespace-nowrap">{formatDateTime(leave.appliedOn)}</td>
|
|
</tr>
|
|
))
|
|
) : (
|
|
<tr>
|
|
<td colSpan={6} className="text-center py-4 text-base-content/50">
|
|
No leaves found
|
|
</td>
|
|
</tr>
|
|
)}
|
|
|
|
{/* {!refreshing && leaves.length > 0 && (
|
|
<tr>
|
|
<td colSpan={6} className="bg-base-200/50">
|
|
<div className="flex justify-center py-2">
|
|
{renderSkeletonRows()}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)} */}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="flex-none flex justify-between items-center text-xs mt-2">
|
|
<div className="text-base-content/60">
|
|
{leaves.length} leave{leaves.length !== 1 ? "s" : ""} found
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
{lastRefreshed && (
|
|
<div className="text-xs text-base-content/60">
|
|
Last updated: {formatLastRefreshed()}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
onClick={handleRefresh}
|
|
disabled={refreshing}
|
|
className="btn btn-xs btn-circle bg-warning"
|
|
title="Refresh Leaves"
|
|
>
|
|
{refreshing ? (
|
|
<Loader2 size={15} className="animate-spin text-base-content" />
|
|
) : (
|
|
<RefreshCw size={15} className="text-base-content" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default LeaveTable; |