diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index e496bff..cd9b1f9 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -183,19 +183,6 @@ ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => { }); -// ipcMain.handle("get-project-logs", async (event, projectID) => { -// try { -// console.log("get-project-logs",projectID) -// const response = await api.get(`/report/project/${projectID}`); -// console.log("response-logs 164" , response) - -// return response; -// } catch (error) { -// console.error("Error fetching project logs:", error); -// throw error; -// } -// }); - ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }) => { try { @@ -378,6 +365,75 @@ ipcMain.handle("create-leave-request", async (event, payload) => { +ipcMain.handle("get-schedules", async () => { + try { + console.log(" Fetching schedules..."); + + const response = await api.get("/payrollSchedule"); // Backend API call + + console.log("✅ Schedule Data:", response.data); + + return { + success: true, + result: response.data + }; + + } catch (error: any) { + console.error("❌ Fetch Schedules Failed:"); + + 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 + }; + } +}); + + +ipcMain.handle("get-payslips", async (event, vaId: string) => { + console.log("get-payslips",vaId) + try { + const response = await api.get(`/paySlip/${vaId}`); + + + return { + success: true, + data: response.data?.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 7a4c939..34c9b41 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -53,6 +53,9 @@ contextBridge.exposeInMainWorld('electron', { createPTOAndSickLeaveRequest: (data) => ipcRenderer.invoke("create-leave-request", data), +getSchedules: () => ipcRenderer.invoke("get-schedules"), + +getPayslips: (vaId: string) => ipcRenderer.invoke("get-payslips", vaId), } satisfies Window['electron']); diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 72f3ab8..7b59fca 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -95,6 +95,7 @@ import Settings from "./pages/Settings/Settings"; import HelpSupport from "./pages/HelpSupport/HelpSupport"; import Vpn from "./pages/Vpn/Vpn"; import Schedule from "./pages/Schedule/Schedule"; +import PaysSlips from "./pages/PaySlips/PaysSlips"; const App: React.FC = () => { @@ -142,6 +143,7 @@ const App: React.FC = () => { } /> } /> } /> + } /> {/* Default Redirect */} diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 4eef5d3..99f7bbf 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -27,6 +27,7 @@ import { ChevronUp, GlobeLock, ClipboardClock, + CircleDollarSign, } from "lucide-react"; import { useActiveAppTracker } from "../hooks/useActiveAppTracker"; import { useDispatch, useSelector } from "react-redux"; @@ -113,6 +114,7 @@ const DashboardLayout = () => { { label: "Timesheet", path: "/dashboard/timesheet", icon: }, { label: "Leave & Adjust", path: "/dashboard/leave-adjustments", icon: }, { label: "Schedule", path: "/dashboard/schedule", icon: }, + { label: "Pays Slips", path: "/dashboard/pays-slips", icon: }, { label: "Settings", path: "/dashboard/settings", icon: }, { label: "VPN", path: "/dashboard/vpn", icon: }, { label: "Help & Support", path: "/dashboard/help-support", icon: }, @@ -297,7 +299,7 @@ const DashboardLayout = () => { ); console.log("Alert Message 2") // Stop tracking - await handleStopTracking(); + // await handleStopTracking(); }, [selectedProject, handleStopTracking]); // Initialize useActiveAppTracker and store finalizeTracking in ref @@ -743,7 +745,7 @@ const DashboardLayout = () => { ))} -
+
+ {errors.comment &&

{errors.comment}

}
{serverError && ( diff --git a/src/ui/pages/PaySlips/PaysSlips.tsx b/src/ui/pages/PaySlips/PaysSlips.tsx new file mode 100644 index 0000000..b54e3c9 --- /dev/null +++ b/src/ui/pages/PaySlips/PaysSlips.tsx @@ -0,0 +1,131 @@ +import { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import { RootState } from "../../store"; + +const PaysSlips = () => { + const { projectSummary } = + useSelector((state: any) => state.projects); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [rows, setRows] = useState([]); + + useEffect(() => { + if (!projectSummary?.vaId) return; + + fetchPayslips(projectSummary?.vaId); + +}, [projectSummary?.vaId]); + + const fetchPayslips = async (vaId: string) => { + setLoading(true); + setError(null); + + try { + console.log("getPayslips" , vaId) + const result = await window.electron.getPayslips(vaId); + + if (!result.success) { + setError(result.message || "Failed to load payslips"); + return; + } + + const response = result.data; + + if (!response || response.length === 0) { + setRows([]); + return; + } + + // Extract payrolls array from first index + const payrolls = response[0]?.payrolls || []; + + setRows(payrolls); + } catch (err: any) { + setError("An unexpected error occurred"); + } finally { + setLoading(false); + } + }; + + return ( + <> +
+
+

Payslips

+
+ +
+ + {loading && ( +
+ +
+ )} + + {error && ( +
{error}
+ )} + + {!loading && !error && rows.length === 0 && ( +
+ No payslip data available. +
+ )} + + {!loading && !error && rows.length > 0 && ( +
+ + + + + + + + + + + + + + {rows.map((row, index) => ( + + + + + + + + + + + + + + ))} + +
#StatusDateTotal HoursTotal PayAction
{index + 1} + + {row.status} + + {row.payrollSchedule}{row.totalHoursFormatted}${row.totalPayAmount.toFixed(2)} + +
+
+ )} +
+
+ + ); +}; + +export default PaysSlips; diff --git a/src/ui/pages/Schedule/Schedule.tsx b/src/ui/pages/Schedule/Schedule.tsx index 36fdcfb..ca8ae10 100644 --- a/src/ui/pages/Schedule/Schedule.tsx +++ b/src/ui/pages/Schedule/Schedule.tsx @@ -1,70 +1,115 @@ +import React, { useEffect, useState } from "react"; +import { DateTime } from "luxon"; + +interface ScheduleItem { + id: string; + isActive: boolean; + status: string | null; + ScheduleDate: string; + cutOffTime: string | null; + periodEnd: string | null; + periodStart: string | null; +} + const Schedule = () => { - const rows = [ - { - period: "Oct 25, 2025 to Nov 07, 2025", - cutoff: "Nov 11, 2025 05:29:59 AM", - payDate: "Nov 14, 2025" - }, - { - period: "Nov 08, 2025 to Nov 21, 2025", - cutoff: "Nov 25, 2025 05:29:59 AM", - payDate: "Nov 28, 2025" - }, - { - period: "Nov 22, 2025 to Dec 05, 2025", - cutoff: "Dec 09, 2025 05:29:59 AM", - payDate: "Dec 12, 2025" - }, - { - period: "Dec 06, 2025 to Dec 19, 2025", - cutoff: "Dec 23, 2025 05:29:59 AM", - payDate: "Dec 26, 2025" - }, - { - period: "Dec 20, 2025 to Jan 02, 2026", - cutoff: "Jan 06, 2026 05:29:59 AM", - payDate: "Jan 09, 2026" - }, - { - period: "Jan 03, 2026 to Jan 16, 2026", - cutoff: "Jan 20, 2026 05:29:59 AM", - payDate: "Jan 23, 2026" - }, - { - period: "Jan 17, 2026 to Jan 30, 2026", - cutoff: "Feb 03, 2026 05:29:59 AM", - payDate: "Feb 06, 2026" - }, - { - period: "Jan 31, 2026 to Feb 13, 2026", - cutoff: "Feb 17, 2026 05:29:59 AM", - payDate: "Feb 20, 2026" - }, - { - period: "Feb 14, 2026 to Feb 27, 2026", - cutoff: "Mar 03, 2026 05:29:59 AM", - payDate: "Mar 06, 2026" - }, - { - period: "Feb 28, 2026 to Mar 13, 2026", - cutoff: "Mar 17, 2026 05:29:59 AM", - payDate: "Mar 20, 2026" - } - ]; + const [rows, setRows] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Format helpers using Luxon + const formatDateTime = (input: string | null) => { + if (!input) return "-"; + return DateTime.fromISO(input) + .setZone("local") + .toLocaleString(DateTime.DATETIME_MED); + }; + + const formatOnlyDate = (input: string | null) => { + if (!input) return "-"; + return DateTime.fromISO(input) + .setZone("local") + .toLocaleString(DateTime.DATE_MED); + }; + + // Fetch schedules + useEffect(() => { + const loadSchedules = async () => { + try { + const res = await window.electron.getSchedules(); + console.log(res.result); + + if (!res.success) { + setError(res.message || "Failed to load schedules"); + return; + } + + const list = res.result || []; // Use data array + setRows(list); + } catch (err: any) { + setError(err.message || "Unexpected error"); + } finally { + setLoading(false); + } + }; + + loadSchedules(); + }, []); + + // Determine if current date is within the schedule period + const isActiveRow = (row: ScheduleItem) => { + if (!row.periodStart || !row.periodEnd) return false; + + const now = DateTime.local(); + const start = DateTime.fromISO(row.periodStart).startOf("day"); + const end = DateTime.fromISO(row.periodEnd).endOf("day"); + + return now >= start && now <= end; + }; + + // Get Active Schedule (only one) + const activeSchedule = rows.find((r) => isActiveRow(r)); return ( - <> -
- {/* Header */} -
-

Schedules

-
+
+ {/* Header */} +
+

Schedules

+ {activeSchedule && ( +
+

+ Pay Date: + {formatOnlyDate(activeSchedule.ScheduleDate)} + +

+
+ )} +
-
+
+ {/* Loader */} + {loading && ( +
+ +
+ )} - {/* ⭐ FIXED: Horizontal scroll wrapper */} + {/* Error */} + {!loading && error && ( +
❌ {error}
+ )} + + {/* No Data */} + {!loading && !error && rows.length === 0 && ( +
No schedules found.
+ )} + + + + + {/* Table */} + {!loading && !error && rows.length > 0 && (
- +
@@ -76,24 +121,24 @@ const Schedule = () => { {rows.map((row, index) => ( - + - - + + ))}
#
{index + 1}{row.period} - {row.cutoff} -
- (Asia/Calcutta) + {formatOnlyDate(row.periodStart)} → {formatOnlyDate(row.periodEnd)}
{row.payDate}{formatDateTime(row.cutOffTime)}{formatOnlyDate(row.ScheduleDate)}
- -
+ )}
- +
); }; diff --git a/types.d.ts b/types.d.ts index 1ec1280..bb7af91 100644 --- a/types.d.ts +++ b/types.d.ts @@ -92,6 +92,36 @@ type LeaveRequestResponse = { message?: string; }; +interface ScheduleItem { + id: string; + isActive: boolean; + status: string | null; + ScheduleDate: string; + cutOffTime: string | null; + periodEnd: string | null; + periodStart: string | null; +} + + +interface PayslipItem { + totalHoursFormatted: string; + payrollSchedule: string; + totalPayAmount: number; + status: string; + date: string | null; + createdDate: string; + name: string; + id: string; +} + +interface PayslipResponse { + payrolls: PayslipItem[]; + vaPortalId: string; + vaName: string; + id: string; +} + + interface Window { electron: { login: (credentials: { email: string; password: string }) => Promise; @@ -174,9 +204,15 @@ interface Window { payload: LeaveRequestPayload ) => Promise; - + getSchedules: () => Promise; + + getPayslips: (vaId: string) => Promise<{ + success: boolean; + data?: PayslipResponse[]; + message?: string; + }>; }; - + }