va-tracker/src/ui/pages/LeaveAndAdjustments/AdjustmentsTable.tsx

183 lines
7.3 KiB
TypeScript

import React from "react";
import { Loader2, RefreshCw } from "lucide-react";
import { DateTime } from "luxon";
interface AdjustmentItem {
id: string;
date: string;
loginTime: string;
logoutTime: string;
comment: string;
status: string;
requestedAt: string;
adjustedHours: string;
project: {
id: string;
name: string;
};
}
interface AdjustmentTableProps {
isTracking: boolean;
adjustments: AdjustmentItem[];
loading: boolean;
lastRefreshed: Date | null;
onRefresh: () => Promise<void>;
}
const AdjustmentsTable: React.FC<AdjustmentTableProps> = ({
isTracking,
adjustments,
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 formatTime = (t: string) => toLocal(t, "HH:mm:ss");
const formatDate = (d: string) => toLocal(d, "yyyy-MM-dd");
const handleRefresh = async () => {
setRefreshing(true);
await onRefresh();
setRefreshing(false);
};
const renderSkeleton = () =>
Array.from({ length: 7 }).map((_, i) => (
<tr key={i}>
<td><div className="skeleton h-4 w-6"></div></td>
<td><div className="skeleton h-4 w-20"></div></td>
<td><div className="skeleton h-4 w-24"></div></td>
<td><div className="skeleton h-4 w-20"></div></td>
<td><div className="skeleton h-4 w-10"></div></td>
<td><div className="skeleton h-4 w-24"></div></td>
<td><div className="skeleton h-4 w-14"></div></td>
</tr>
));
return (
<>
<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 text-[12px] z-10">
<tr className="text-center">
<th>#</th>
<th>Project</th>
<th>Date</th>
<th>In → Out</th>
<th>Adjusted Hours</th>
<th>Status</th>
<th>Requested At</th>
</tr>
</thead>
<tbody className="text-center">
{loading
? renderSkeleton()
: adjustments.length === 0
? (
<tr>
<td colSpan={7} className="text-center py-4 text-base-content/50">
No adjustments found
</td>
</tr>
)
: adjustments.map((item, idx) => (
<tr key={item.id}>
<td>{idx + 1}</td>
<td>
<span className="badge badge-xs badge-info whitespace-nowrap">
{item.project?.name || "N/A"}
</span>
</td>
<td className="whitespace-nowrap">{formatDate(item.date)}</td>
<td className="whitespace-nowrap flex ">
<span className="me-1">{formatTime(item.loginTime)} </span>
→ {" "}
<span className="ms-1">{formatTime(item.logoutTime)}</span>
</td>
<td className="whitespace-nowrap">
<span className="badge badge-xs badge-primary">
{item.adjustedHours}
</span>
</td>
<td>
<span
className={`badge badge-xs ${
item.status === "PENDING"
? "badge-warning"
: item.status === "APPROVED"
? "badge-success"
: "badge-error"
}`}
>
{item.status}
</span>
</td>
<td className="whitespace-nowrap">{toLocal(item.requestedAt, "yyyy-MM-dd HH:mm:ss")}</td>
</tr>
))}
{/* {refreshing && adjustments.length > 0 && (
<tr>
<td colSpan={7} className="bg-base-200/50">
<div className="flex justify-center py-2">
{renderSkeleton()}
</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">
{adjustments.length} adjustment{adjustments.length !== 1 ? "s" : ""} found
</div>
<div className="flex items-center gap-4">
{lastRefreshed && (
<div className="text-base-content/60">
Last updated: {toLocal(lastRefreshed.toISOString(), "HH:mm:ss")}
</div>
)}
<button
onClick={handleRefresh}
disabled={refreshing}
className="btn btn-xs btn-circle bg-warning"
>
{refreshing ? (
<Loader2 size={14} className="animate-spin" />
) : (
<RefreshCw size={14} />
)}
</button>
</div>
</div>
</>
);
};
export default AdjustmentsTable;