Merge branch 'window-build' of http://git.hicglobalsolutions.in/BhawanSingh/va-tracker - 30-11 Merge Window-build
This commit is contained in:
commit
3ef615a160
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
|||
<Route path="vpn" element={<Vpn />} />
|
||||
<Route path="help-support" element={<HelpSupport />} />
|
||||
<Route path="schedule" element={<Schedule/>} />
|
||||
<Route path="pays-slips" element={<PaysSlips/>} />
|
||||
</Route>
|
||||
|
||||
{/* Default Redirect */}
|
||||
|
|
|
|||
|
|
@ -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: <TimerIcon size={18} /> },
|
||||
{ label: "Leave & Adjust", path: "/dashboard/leave-adjustments", icon: <CalendarDays size={18} /> },
|
||||
{ label: "Schedule", path: "/dashboard/schedule", icon: <ClipboardClock size={18} />},
|
||||
{ label: "Pays Slips", path: "/dashboard/pays-slips", icon: <CircleDollarSign size={18} />},
|
||||
{ label: "Settings", path: "/dashboard/settings", icon: <SettingsIcon size={18} /> },
|
||||
{ label: "VPN", path: "/dashboard/vpn", icon: <GlobeLock size={18} /> },
|
||||
{ label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> },
|
||||
|
|
@ -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 = () => {
|
|||
</div>
|
||||
))}
|
||||
|
||||
<div className="absolute bottom-13 ">
|
||||
<div className={`bsolute ${!isTracking ? "bottom-13" : "bottom-5"} `}>
|
||||
<label className="swap swap-rotate p-2.5">
|
||||
{/* this hidden checkbox controls the state */}
|
||||
<input type="checkbox" className="theme-controller" onChange={toggleTheme}
|
||||
|
|
|
|||
|
|
@ -258,8 +258,8 @@ const PtoLeaveTab: React.FC<{
|
|||
onChange={(val) => handleChange('comment', val)}
|
||||
className="h-60"
|
||||
/>
|
||||
{errors.comment && <p className="text-error text-xs">{errors.comment}</p>}
|
||||
</div>
|
||||
{errors.comment && <p className="text-error text-xs">{errors.comment}</p>}
|
||||
</div>
|
||||
|
||||
{serverError && (
|
||||
|
|
|
|||
|
|
@ -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<string | null>(null);
|
||||
const [rows, setRows] = useState<any[]>([]);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className="flex flex-col gap-2 max-h-screen">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-md font-bold">Payslips</h2>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto rounded-box border border-base-content/10 bg-base-100 shadow-md max-h-[80vh] min-h-[83vh] no-scrollbar">
|
||||
|
||||
{loading && (
|
||||
<div className="flex justify-center py-10">
|
||||
<span className="loading loading-spinner loading-md"></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="text-center text-red-500 py-5">{error}</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && rows.length === 0 && (
|
||||
<div className="text-center py-10 text-gray-500">
|
||||
No payslip data available.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && rows.length > 0 && (
|
||||
<div className="overflow-x-auto w-full no-scrollbar">
|
||||
<table className="table table-zebra w-full text-[12px] min-w-[650px]">
|
||||
<thead className="sticky top-0 bg-base-200 z-10">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
<th>Total Hours</th>
|
||||
<th>Total Pay</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{rows.map((row, index) => (
|
||||
<tr key={row.id}>
|
||||
<td>{index + 1}</td>
|
||||
|
||||
<td>
|
||||
<span
|
||||
className={`badge ${
|
||||
row.status === "Paid"
|
||||
? "badge-success"
|
||||
: "badge-warning"
|
||||
}`}
|
||||
>
|
||||
{row.status}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>{row.payrollSchedule}</td>
|
||||
|
||||
<td>{row.totalHoursFormatted}</td>
|
||||
|
||||
<td>${row.totalPayAmount.toFixed(2)}</td>
|
||||
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-xs btn-primary"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaysSlips;
|
||||
|
|
@ -1,70 +1,115 @@
|
|||
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"
|
||||
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, setRows] = useState<ScheduleItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(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 (
|
||||
<>
|
||||
<div className="flex flex-col gap-2 max-h-screen">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-md font-bold">Schedules</h2>
|
||||
{activeSchedule && (
|
||||
<div className="p-1 bg-secondary text-secondary-content rounded-lg">
|
||||
<p className="text-xs">
|
||||
Pay Date: <span className="font-semibold">
|
||||
{formatOnlyDate(activeSchedule.ScheduleDate)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto rounded-box border border-base-content/10 bg-base-100 shadow-md max-h-[80vh] min-h-[83vh] no-scrollbar">
|
||||
{/* Loader */}
|
||||
{loading && (
|
||||
<div className="flex justify-center items-center h-full">
|
||||
<span className="loading loading-spinner text-primary"></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ⭐ FIXED: Horizontal scroll wrapper */}
|
||||
{/* Error */}
|
||||
{!loading && error && (
|
||||
<div className="text-center text-red-500 py-10">❌ {error}</div>
|
||||
)}
|
||||
|
||||
{/* No Data */}
|
||||
{!loading && !error && rows.length === 0 && (
|
||||
<div className="text-center text-gray-500 py-10">No schedules found.</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Table */}
|
||||
{!loading && !error && rows.length > 0 && (
|
||||
<div className="overflow-x-auto w-full no-scrollbar">
|
||||
<table className="table table-zebra w-full text-[12px] min-w-[600px]">
|
||||
<table className="table w-full text-[12px] min-w-[600px]">
|
||||
<thead className="sticky top-0 bg-base-200 z-10">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
|
|
@ -76,24 +121,24 @@ const Schedule = () => {
|
|||
|
||||
<tbody>
|
||||
{rows.map((row, index) => (
|
||||
<tr key={index}>
|
||||
<tr
|
||||
key={row.id}
|
||||
className={isActiveRow(row) ? "bg-secondary font-semibold" : ""}
|
||||
>
|
||||
<td>{index + 1}</td>
|
||||
<td>{row.period}</td>
|
||||
<td>
|
||||
{row.cutoff}
|
||||
<br />
|
||||
<span className="text-gray-500 text-[11px]">(Asia/Calcutta)</span>
|
||||
{formatOnlyDate(row.periodStart)} → {formatOnlyDate(row.periodEnd)}
|
||||
</td>
|
||||
<td>{row.payDate}</td>
|
||||
<td>{formatDateTime(row.cutOffTime)}</td>
|
||||
<td>{formatOnlyDate(row.ScheduleDate)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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<any>;
|
||||
|
|
@ -174,7 +204,13 @@ interface Window {
|
|||
payload: LeaveRequestPayload
|
||||
) => Promise<LeaveRequestResponse>;
|
||||
|
||||
getSchedules: () => Promise<any>;
|
||||
|
||||
getPayslips: (vaId: string) => Promise<{
|
||||
success: boolean;
|
||||
data?: PayslipResponse[];
|
||||
message?: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue