diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 759d285..8c50481 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -462,6 +462,35 @@ ipcMain.handle("get-leave-list", async () => { } }); +ipcMain.handle("get-adjustments-list", async () => { + + try { + const res = await api.get("/timesheet/adjustments/list"); + + console.log("/timesheet/adjustments/list", res.data); + return { + success: true, + data: res.data, + }; + } catch (error: any) { + let message = "Unknown error occurred"; + + if (error.response) { + console.log("Status:", error.response.status); + console.log("Message:", error.response.statusText); + console.log("Server Response:", error.response.data); + + message = error.response.data?.message || error.response.statusText; + } else { + message = error.message; + } + return { + success: false, + message + }; + } +}); + export const sendTokenExpired = () => { diff --git a/src/electron/preload.cts b/src/electron/preload.cts index 3ebc272..142079a 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -51,12 +51,13 @@ contextBridge.exposeInMainWorld('electron', { createTimesheet: (data: any) => ipcRenderer.invoke('create-timesheet', data), createPTOAndSickLeaveRequest: (data) => - ipcRenderer.invoke("create-leave-request", data), + ipcRenderer.invoke("create-leave-request", data), -getSchedules: () => ipcRenderer.invoke("get-schedules"), + getSchedules: () => ipcRenderer.invoke("get-schedules"), -getPayslips: (vaId: string) => ipcRenderer.invoke("get-payslips", vaId), - getLeaveList: () => ipcRenderer.invoke("get-leave-list") + getPayslips: (vaId: string) => ipcRenderer.invoke("get-payslips", vaId), + getLeaveList: () => ipcRenderer.invoke("get-leave-list"), + getAdjustmentsList:() => ipcRenderer.invoke("get-adjustments-list"), } satisfies Window['electron']); diff --git a/src/ui/pages/LeaveAndAdjustments/AdjustmentsTable.tsx b/src/ui/pages/LeaveAndAdjustments/AdjustmentsTable.tsx new file mode 100644 index 0000000..0e0e65d --- /dev/null +++ b/src/ui/pages/LeaveAndAdjustments/AdjustmentsTable.tsx @@ -0,0 +1,183 @@ +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; +} + +const AdjustmentsTable: React.FC = ({ + 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) => ( + +
+
+
+
+
+
+
+ + )); + + return ( + <> +
+ + + + + + + + + + + + + + + {loading + ? renderSkeleton() + : adjustments.length === 0 + ? ( + + + + ) + : adjustments.map((item, idx) => ( + + + + + + + + + + + + + + + + ))} + + {/* {refreshing && adjustments.length > 0 && ( + + + + )} */} + +
#ProjectDateIn → OutAdjusted HoursStatusRequested At
+ No adjustments found +
{idx + 1} + + {item.project?.name || "N/A"} + + {formatDate(item.date)} + {formatTime(item.loginTime)} + → {" "} + {formatTime(item.logoutTime)} + + + {item.adjustedHours} + + + + {item.status} + + {toLocal(item.requestedAt, "yyyy-MM-dd HH:mm:ss")}
+
+ {renderSkeleton()} +
+
+
+ + {/* FOOTER */} +
+
+ {adjustments.length} adjustment{adjustments.length !== 1 ? "s" : ""} found +
+ +
+ {lastRefreshed && ( +
+ Last updated: {toLocal(lastRefreshed.toISOString(), "HH:mm:ss")} +
+ )} + + +
+
+ + ); +}; + +export default AdjustmentsTable; \ No newline at end of file diff --git a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx index 1ab0085..c87ca3c 100644 --- a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx +++ b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx @@ -1,8 +1,9 @@ -import React, { useEffect, useState } from "react"; +import React, { useState, useEffect } from "react"; import { useNavigate, useOutletContext } from "react-router-dom"; import { TrackingState } from "../../layout/DashboardLayout"; -import { Loader2, RefreshCw } from "lucide-react"; -import { DateTime } from "luxon"; +import LeaveTable from "./LeaveTable"; +import AdjustmentsTable from "./AdjustmentsTable"; +import { useSelector } from "react-redux"; interface LeaveItem { leaveId: string; @@ -17,70 +18,104 @@ interface LeaveItem { rejectionReason: string | null; } +interface AdjustmentItem { + id: string; + date: string; + loginTime: string; + logoutTime: string; + comment: string; + status: string; + requestedAt: string; + adjustedHours: string; + project: { + id: string; + name: string; + }; +} + +interface ApiResponse { + success: boolean; + data?: T; +} + const LeaveAndAdjustments: React.FC = () => { const { isTracking } = useOutletContext(); const navigate = useNavigate(); + const { projectSummary } = useSelector((state: any) => state.projects); +console.log("projectSummary46" , projectSummary) + + // Tabs state + const [activeTab, setActiveTab] = useState<"leave" | "adjustments">("leave"); + + // Data states const [leaves, setLeaves] = useState([]); - const [loading, setLoading] = useState(true); - const [refreshing, setRefreshing] = useState(false); + const [adjustments, setAdjustments] = useState([]); + + // Loading states + const [loadingLeaves, setLoadingLeaves] = useState(true); + const [loadingAdjustments, setLoadingAdjustments] = useState(true); + + // Refresh state const [lastRefreshed, setLastRefreshed] = useState(null); - const fetchLeaves = async () => { - try { - const res = await window.electron.getLeaveList(); - - if (res.success) { - setLeaves(res?.data?.leaves || []); - } - setLastRefreshed(new Date()); - } catch (e) { - console.error("Error fetching leaves:", e); - } finally { - setLoading(false); - setRefreshing(false); - } - }; - - const handleRefresh = async () => { - setRefreshing(true); - await fetchLeaves(); - }; - - useEffect(() => { - fetchLeaves(); - }, []); - const handleApplyLeaveAndAdjust = () => { navigate("/dashboard/leave-adjustments/apply"); }; - const renderSkeletonRows = () => { - return Array.from({ length: 5 }).map((_, index) => ( - -
-
-
-
-
-
- - )); + // Fetch leaves data + const fetchLeaves = async () => { + setLoadingLeaves(true); + try { + const res: ApiResponse<{ leaves: LeaveItem[] }> = await window.electron.getLeaveList(); + if (res.success) { + setLeaves(res?.data?.leaves || []); + } + } catch (e) { + console.error("Error fetching leaves:", e); + } finally { + setLoadingLeaves(false); + } }; - const formatLastRefreshed = () => { - if (!lastRefreshed) return "Never"; - - return DateTime.fromJSDate(lastRefreshed) - .toLocal() - .toFormat("HH:mm:ss"); // Only time - }; + // Fetch adjustments data + const fetchAdjustments = async () => { + setLoadingAdjustments(true); + try { + const res: ApiResponse = await window.electron.getAdjustmentsList(); + if (res.success) { + setAdjustments(res.data || []); + } + } catch (err) { + console.error("Error fetching adjustments:", err); + } finally { + setLoadingAdjustments(false); + } + }; + + // Combined refresh function + const handleRefresh = async () => { + if (activeTab === "leave") { + await fetchLeaves(); + } else { + await fetchAdjustments(); + } + setLastRefreshed(new Date()); + }; + + // Initial data fetch + useEffect(() => { + fetchLeaves(); + fetchAdjustments(); + setLastRefreshed(new Date()); + }, []); + return (
{/* Header */}
@@ -90,115 +125,48 @@ const LeaveAndAdjustments: React.FC = () => {
- {/* Table Container */} -
- - - - - - - - - - - + {/* Tabs */} +
+ -
- {loading ? ( - renderSkeletonRows() - ) : refreshing && leaves.length === 0 ? ( - renderSkeletonRows() - ) : leaves.length > 0 ? ( - leaves.map((leave, index) => ( - - - - - - - - - - - - - - )) - ) : ( - - - - )} - - {refreshing && leaves.length > 0 && ( - - - - )} - -
#Leave TypeDate From → ToStatusTotal DaysApplied On
{index + 1} - {leave.leaveType} - - {leave.dateFrom} -
- {leave.dateTo} -
- - {leave.status} - - {leave.totalDays}{new Date(leave.appliedOn).toLocaleString()}
- No leaves found -
-
- - Refreshing... -
-
+
- {/* Footer */} -
-
- {leaves.length} leave{leaves.length !== 1 ? "s" : ""} found -
- -
- {lastRefreshed && ( -
- Last updated: {formatLastRefreshed()} -
- )} - - -
+ {/* TABLE AREA - Scrollable */} +
+ {activeTab === "leave" && ( + + )} + {activeTab === "adjustments" && ( + + )}
); }; -export default LeaveAndAdjustments; +export default LeaveAndAdjustments; \ No newline at end of file diff --git a/src/ui/pages/LeaveAndAdjustments/LeaveTable.tsx b/src/ui/pages/LeaveAndAdjustments/LeaveTable.tsx new file mode 100644 index 0000000..8ffd576 --- /dev/null +++ b/src/ui/pages/LeaveAndAdjustments/LeaveTable.tsx @@ -0,0 +1,184 @@ +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; +} + +const LeaveTable: React.FC = ({ + 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) => ( + +
+
+
+
+
+
+ + )); + + const formatLastRefreshed = () => + lastRefreshed + ? DateTime.fromJSDate(lastRefreshed) + .setZone(DateTime.local().zoneName) + .toFormat("HH:mm:ss") + : "Never"; + + return ( + <> + {/* Table Container */} +
+ + + + + + + + + + + + + + {loading ? ( + renderSkeletonRows() + ) : refreshing && leaves.length === 0 ? ( + renderSkeletonRows() + ) : leaves.length > 0 ? ( + leaves.map((leave, index) => ( + + + + + + + + + + + + + + )) + ) : ( + + + + )} + + {/* {!refreshing && leaves.length > 0 && ( + + + + )} */} + +
#Leave TypeDate From → ToStatusTotal DaysApplied On
{index + 1} + + {leave.leaveType} + + + {formatDate(leave.dateFrom)} + → {" "} + {formatDate(leave.dateTo)} + + + {leave.status} + + {leave.totalDays}{formatDateTime(leave.appliedOn)}
+ No leaves found +
+
+ {renderSkeletonRows()} +
+
+
+ + {/* Footer */} +
+
+ {leaves.length} leave{leaves.length !== 1 ? "s" : ""} found +
+ +
+ {lastRefreshed && ( +
+ Last updated: {formatLastRefreshed()} +
+ )} + + +
+
+ + ); +}; + +export default LeaveTable; \ No newline at end of file diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx index fb85198..516127f 100644 --- a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx +++ b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx @@ -662,7 +662,7 @@ const AdjustmentTab: React.FC = ({ projects }) => { diff --git a/src/ui/pages/Timesheets/Timesheets.tsx b/src/ui/pages/Timesheets/Timesheets.tsx index 98e893f..a73d773 100644 --- a/src/ui/pages/Timesheets/Timesheets.tsx +++ b/src/ui/pages/Timesheets/Timesheets.tsx @@ -133,13 +133,13 @@ const Timesheets: React.FC = () => { setLogs([]); } - + } catch (error) { console.error("Error fetching logs:", error); setModalOpen(false) } finally { setLoading(false); - + } }; diff --git a/types.d.ts b/types.d.ts index 6eb395d..495aa44 100644 --- a/types.d.ts +++ b/types.d.ts @@ -83,7 +83,7 @@ interface ProfileData { paymentMethod: string; payoneerAccount: string; veemAccount: string; - wiseEmail:string + wiseEmail: string _id: string; } @@ -217,6 +217,11 @@ interface Window { success: boolean; data?: any; }>; + + getAdjustmentsList: () => Promise<{ + success: boolean; + data?: any; + }>; }; }