183 lines
7.3 KiB
TypeScript
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; |