- {isStopping ? (
- // Skeleton for left side during stop
-
- ) : selectedProject ? (
- isTracking ? (
-
+
+ {/* Header */}
+ {isTracking &&
+
+
+ {isStopping ? (
+ // Skeleton for left side during stop
+
+ ) : selectedProject ? (
+ isTracking ? (
+
+ ) : (
+
+ )
) : (
- )
- ) : (
+ )}
+
+ {!isStopping && selectedProject ? (
+
+ {selectedProject.projectName}
+
+ ) : !isStopping ? (
+
+ No Project Selected
+
+ ) : null}
+
+
+
+ {isStopping ? (
+ // Skeleton for right side during stop
+
+ ) : (
+ <>
+
+ {formatTime(
+ isTracking ? elapsedTime : parseTimeStringToSeconds(selectedProject?.totalWorkTime || "0h 0m 0s")
+ )}
+
+
+ {/*
+
+ */}
+
+
+ >
+ )}
+
+
+ }
+
+ {/* Layout Body */}
+
+ {/* Sidebar */}
+
- {!isStopping && selectedProject ? (
-
- {selectedProject.projectName}
-
- ) : !isStopping ? (
-
- No Project Selected
-
- ) : null}
-
+ {/* Main Content */}
+
+
+
-
- {isStopping ? (
- // Skeleton for right side during stop
-
- ) : (
- <>
-
- {formatTime(
- isTracking ? elapsedTime : parseTimeStringToSeconds(selectedProject?.totalWorkTime || "0h 0m 0s")
- )}
-
-
-
-
-
-
-
- >
- )}
-
-
- }
-
- {/* Layout Body */}
-
- {/* Sidebar */}
-
-
- {/* Main Content */}
-
-
-
-
-
setShowLogoutModal(false)}
- onConfirm={handleLogout}
- />
+ setShowLogoutModal(false)}
+ onConfirm={handleLogout}
+ />
+
-
>
);
};
diff --git a/src/ui/pages/Dashboard.tsx b/src/ui/pages/Dashboard.tsx
index 26d8220..5506739 100644
--- a/src/ui/pages/Dashboard.tsx
+++ b/src/ui/pages/Dashboard.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from "react";
-import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react";
+import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause, RefreshCw } from "lucide-react";
import { useOutletContext } from "react-router-dom";
import NoData from "./NoData";
import { useDispatch, useSelector } from "react-redux";
@@ -16,7 +16,7 @@ interface Project {
totalWorkTime: string;
totalWorkMs: number;
totalLogs: number;
- status: string
+ status: string;
}
interface ProjectTrackingData {
@@ -105,6 +105,7 @@ const Dashboard: React.FC = () => {
const selectedProject = useSelector((state: any) => state.projects.selectedProject);
const projectSummary = useSelector((state: any) => state.projects.projectSummary);
const projectTrackingMap = useSelector((state: any) => state.projects.projectTrackingMap);
+ const [isRefreshing, setIsRefreshing] = useState(false);
console.log("Line 111", projectsData, selectedProject, projectTrackingMap)
@@ -127,60 +128,69 @@ const Dashboard: React.FC = () => {
}
};
- useEffect(() => {
- const fetchProjects = async () => {
- try {
- console.log("๐ก Fetching projects...Dashboard");
- const res = await window.electron.getProjectList();
- console.log(" Project list response in dashboard:", res);
+ const fetchProjects = async () => {
+ setIsRefreshing(true)
+ try {
+ console.log("๐ก Fetching projects...Dashboard");
+ const res = await window.electron.getProjectList();
+ console.log(" Project list response in dashboard:", res);
- if (res?.success && Array.isArray(res.data)) {
- // Store data redux store
- dispatch(setProjectsDataSlice(res.data));
+ if (res?.success && Array.isArray(res.data)) {
+ // Store data redux store
+ dispatch(setProjectsDataSlice(res.data));
+ dispatch(
+ setProjectSummarySlice({
+ totalProjects: res.totalProjects || 0,
+ activeProjects: res.activeProjects || 0,
+ awayProjects: res.awayProjects || 0,
+ totalWorkTime: res.totalWorkTime || "0h 0m 0s",
+ totalWorkMs: res.totalWorkMs || 0,
+ vaName: res.vaName || "",
+ vaId: res.vaId || "",
+ })
+ );
+
+ // Auto-select first project
+ if (res.data.length > 0 && !selectedProject) {
+ const firstProject = res.data[0];
+
+ // Select the project
+ dispatch(setSelectedProjectSlice(firstProject));
+
+ const initialElapsed = parseTimeStringToSeconds(firstProject.totalWorkTime);
+
+ // Initialize tracking data for first project
dispatch(
- setProjectSummarySlice({
- totalProjects: res.totalProjects || 0,
- activeProjects: res.activeProjects || 0,
- awayProjects: res.awayProjects || 0,
- totalWorkTime: res.totalWorkTime || "0h 0m 0s",
- totalWorkMs: res.totalWorkMs || 0,
- vaName: res.vaName || "",
- vaId: res.vaId || "",
+ initializeProjectTracking({
+ projectId: firstProject.projectId,
+ initialTime: initialElapsed,
})
);
-
- // Auto-select first project
- if (res.data.length > 0 && !selectedProject) {
- const firstProject = res.data[0];
-
- // Select the project
- dispatch(setSelectedProjectSlice(firstProject));
-
- const initialElapsed = parseTimeStringToSeconds(firstProject.totalWorkTime);
-
- // Initialize tracking data for first project
- dispatch(
- initializeProjectTracking({
- projectId: firstProject.projectId,
- initialTime: initialElapsed,
- })
- );
- }
-
- fetchProfile()
-
- } else {
- console.warn("โ ๏ธ Unexpected response:", res);
}
- } catch (err) {
- console.error("โ Error fetching projects:", err);
- }
- };
+ fetchProfile()
+
+ } else {
+ console.warn("โ ๏ธ Unexpected response:", res);
+ }
+ } catch (err) {
+ console.error("โ Error fetching projects:", err);
+ } finally {
+ setIsRefreshing(false)
+ }
+ };
+
+ useEffect(() => {
fetchProjects();
}, []);
+ const handleRefresh = async () => {
+ // setIsRefreshing(true);
+ await fetchProjects();
+ // setTimeout(() => setIsRefreshing(false), 500); // Small delay for visual feedback
+ };
+
const selectedData = selectedProject
? projectTrackingMap[selectedProject.projectId]
: null;
@@ -234,7 +244,7 @@ const Dashboard: React.FC = () => {
};
return (
-
+
{/* Header */}
Dashboard
@@ -243,7 +253,7 @@ const Dashboard: React.FC = () => {
- {isStopping ? (
+ {(isStopping || isRefreshing) ? (
// Skeleton loader for tracking section
@@ -318,10 +328,19 @@ const Dashboard: React.FC = () => {
{/* ๐น Sticky Header */}
{/* ๐น Table or Empty State or Skeleton */}
- {isStopping ? (
+ {(isStopping || isRefreshing) ? (
// Skeleton loader for projects table
{[...Array(3)].map((_, index) => (
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 (
+ <>
+
+
+
+
+ | # |
+ Project |
+ Date |
+ In โ Out |
+ Adjusted Hours |
+ Status |
+ Requested At |
+
+
+
+
+ {loading
+ ? renderSkeleton()
+ : adjustments.length === 0
+ ? (
+
+ |
+ No adjustments found
+ |
+
+ )
+ : adjustments.map((item, idx) => (
+
+ | {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")} |
+
+ ))}
+
+ {/* {refreshing && adjustments.length > 0 && (
+
+ |
+
+ {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/ApplyLeaveAndAdjustmentsTime.tsx b/src/ui/pages/LeaveAndAdjustments/ApplyLeaveAndAdjustmentsTime.tsx
new file mode 100644
index 0000000..4b5b4a7
--- /dev/null
+++ b/src/ui/pages/LeaveAndAdjustments/ApplyLeaveAndAdjustmentsTime.tsx
@@ -0,0 +1,114 @@
+import { useState } from "react";
+import { useSelector } from "react-redux";
+import { RootState } from "../../store";
+import PtoLeaveTab from './tabs/PtoLeaveTab';
+import SickLeaveTab from './tabs/SickLeaveTab';
+import AdjustmentTab from './tabs/AdjustmentTab';
+import { useNavigate } from "react-router-dom";
+
+type TabType = 'pto' | 'sick' | 'adjustment';
+
+const ApplyLeaveAndAdjustmentsTime = () => {
+ const [activeTab, setActiveTab] = useState('pto');
+ const [isSubmitting, setIsSubmitting] = useState(false); // NEW
+ const projects = useSelector((state: RootState) => state.projects.projectsData);
+
+ const navigate = useNavigate()
+
+ // ------- COMMON API CALL FOR BOTH TABS -------
+ const submitLeave = async (payload: {
+ dateFrom: string;
+ dateTo: string;
+ leaveType: "PTO" | "SICK";
+ comment: string;
+ }) => {
+ try {
+ setIsSubmitting(true);
+
+ console.log("Leave /sick Payload:", payload);
+
+ const response = await window.electron.createPTOAndSickLeaveRequest(payload)
+ console.log("Leave Payload:", response)
+ if (response.success) {
+ return { success: true };
+ }
+
+ return {
+ success: false,
+ message: response.message
+ };
+ } catch (err) {
+ console.error(err);
+ return { success: false, message: "Request failed" };
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+
+ const handleBack = () => {
+ navigate('/dashboard/leave-adjustments')
+ }
+ return (
+ <>
+
+
+
+
Apply Leave & Adjust Timesheet
+
+
+ {/* Tabs โ Disabled while submitting */}
+
+
+ {/* Main Container */}
+
+
+
+ {activeTab === "pto" && (
+
+ )}
+
+ {activeTab === "sick" && (
+
+ )}
+
+ {activeTab === "adjustment" && (
+
+ )}
+
+
+
+
+
+ >
+ )
+}
+
+export default ApplyLeaveAndAdjustmentsTime
\ No newline at end of file
diff --git a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx
index 8c71d47..e6e728f 100644
--- a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx
+++ b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx
@@ -1,104 +1,172 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate, useOutletContext } from "react-router-dom";
+import { TrackingState } from "../../layout/DashboardLayout";
+import LeaveTable from "./LeaveTable";
+import AdjustmentsTable from "./AdjustmentsTable";
+import { useSelector } from "react-redux";
-import React, { useState } from 'react';
-import { useSelector } from 'react-redux';
-import { RootState } from '../../store';
-import PtoLeaveTab from './tabs/PtoLeaveTab';
-import SickLeaveTab from './tabs/SickLeaveTab';
-import AdjustmentTab from './tabs/AdjustmentTab';
+interface LeaveItem {
+ leaveId: string;
+ dateFrom: string;
+ dateTo: string;
+ totalDays: number;
+ leaveType: string;
+ comment: string;
+ status: string;
+ appliedOn: string;
+ reviewedAt: string | null;
+ rejectionReason: string | null;
+}
-type TabType = 'pto' | 'sick' | 'adjustment';
+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 [activeTab, setActiveTab] = useState('pto');
- const [isSubmitting, setIsSubmitting] = useState(false); // ๐ NEW
- const projects = useSelector((state: RootState) => state.projects.projectsData);
+ const { isTracking } = useOutletContext();
+ const navigate = useNavigate();
- // ------- COMMON API CALL FOR BOTH TABS -------
- const submitLeave = async (payload: {
- dateFrom: string;
- dateTo: string;
- leaveType: "PTO" | "SICK";
- comment: string;
- }) => {
+ 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 [adjustments, setAdjustments] = useState([]);
+
+ // Loading states
+ const [loadingLeaves, setLoadingLeaves] = useState(true);
+ const [loadingAdjustments, setLoadingAdjustments] = useState(true);
+
+ // Refresh state
+ const [lastRefreshed, setLastRefreshed] = useState(null);
+
+ const handleApplyLeaveAndAdjust = () => {
+ navigate("/dashboard/leave-adjustments/apply");
+ };
+
+ // Fetch leaves data
+ const fetchLeaves = async () => {
+ setLoadingLeaves(true);
try {
- setIsSubmitting(true);
-
- console.log("Leave /sick Payload:", payload);
-
- const response = await window.electron.createPTOAndSickLeaveRequest(payload)
- console.log("Leave Payload:", response)
- if (response.success) {
- return { success: true };
+ const res: ApiResponse<{ leaves: LeaveItem[] }> = await window.electron.getLeaveList();
+ if (res.success) {
+ setLeaves(res?.data?.leaves || []);
}
-
- return {
- success: false,
- message: response.message
- };
- } catch (err) {
- console.error(err);
- return { success: false, message: "Request failed" };
+ } catch (e) {
+ console.error("Error fetching leaves:", e);
} finally {
- setIsSubmitting(false);
+ setLoadingLeaves(false);
}
};
+ // Fetch adjustments data
+ const fetchAdjustments = async () => {
+ setLoadingAdjustments(true);
+ try {
+ const res: ApiResponse = await window.electron.getAdjustmentsList(projectSummary?.vaId);
+ 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 (
-
-
-
Leave & Adjust Timesheet
-
- {/* Tabs โ Disabled while submitting */}
-
-
- {/* Main Container */}
-
-
-
- {activeTab === "pto" && (
-
- )}
-
- {activeTab === "sick" && (
-
- )}
-
- {activeTab === "adjustment" && (
-
- )}
-
-
-
+
+ {/* Header */}
+
+
Leave & Adjust Timesheet
+
-
+
+ {/* Tabs */}
+
+
+
+
+
+
+ {/* 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 */}
+
+
+
+
+ | # |
+ Leave Type |
+ Date From โ To |
+ Status |
+ Total Days |
+ Applied On |
+
+
+
+
+ {loading ? (
+ renderSkeletonRows()
+ ) : refreshing && leaves.length === 0 ? (
+ renderSkeletonRows()
+ ) : leaves.length > 0 ? (
+ leaves.map((leave, index) => (
+
+ | {index + 1} |
+
+
+
+ {leave.leaveType}
+
+ |
+
+
+ {formatDate(leave.dateFrom)}
+ โ {" "}
+ {formatDate(leave.dateTo)}
+ |
+
+
+
+ {leave.status}
+
+ |
+
+ {leave.totalDays} |
+
+ {formatDateTime(leave.appliedOn)} |
+
+ ))
+ ) : (
+
+ |
+ No leaves found
+ |
+
+ )}
+
+ {/* {!refreshing && leaves.length > 0 && (
+
+ |
+
+ {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 f923f9a..516127f 100644
--- a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx
+++ b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx
@@ -4,6 +4,7 @@ import ReactQuill from 'react-quill-new';
import { quillModules } from '../../../utils/quillConfig';
import Quill from 'quill';
import 'quill/dist/quill.snow.css';
+import { useNavigate } from 'react-router-dom';
interface Project {
projectId: string;
@@ -48,6 +49,7 @@ const AdjustmentTab: React.FC = ({ projects }) => {
const [comment, setComment] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const quillRef = useRef(null);
+ const navigate = useNavigate()
// Field validation functions
const validateProjectId = (projectId: string): string | undefined => {
@@ -323,6 +325,12 @@ const AdjustmentTab: React.FC = ({ projects }) => {
useEffect(() => {
if (selectedProjectId && selectedDate) {
+ setProjectSummary(null)
+ setStartTime("");
+ setEndTime("");
+ setComment("");
+ setFormErrors({});
+ setTotalAdjustment("0h 0m");
fetchLogs();
} else {
setProjectSummary(null);
@@ -454,6 +462,7 @@ const AdjustmentTab: React.FC = ({ projects }) => {
console.log("Timesheet Created Successfully");
alert(res.message);
resetForm();
+ navigate('/dashboard/leave-adjustments')
} else {
setFormErrors(prev => ({
...prev,
@@ -653,13 +662,8 @@ const AdjustmentTab: React.FC = ({ projects }) => {
diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx
index ba531d8..1bd14a1 100644
--- a/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx
+++ b/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx
@@ -4,6 +4,7 @@ import { quillModules } from "../../../utils/quillConfig";
import ReactQuill from "react-quill-new";
import Quill from "quill";
import "quill/dist/quill.snow.css";
+import { useNavigate } from "react-router-dom";
interface LeaveFormData {
fromDate: string;
@@ -23,6 +24,7 @@ const PtoLeaveTab: React.FC<{
});
const [serverError, setServerError] = useState("");
const quillRef = useRef(null);
+ const navigate = useNavigate()
const [errors, setErrors] = useState>({});
@@ -198,7 +200,8 @@ const PtoLeaveTab: React.FC<{
if (result.success) {
resetForm();
- alert("PTO submitted!");
+ alert("PTO Request submitted!");
+ navigate('/dashboard/leave-adjustments')
} else {
setServerError(result.message || "Something went wrong");
}
@@ -258,8 +261,8 @@ const PtoLeaveTab: React.FC<{
onChange={(val) => handleChange('comment', val)}
className="h-60"
/>
- {errors.comment && {errors.comment}
}
+ {errors.comment &&
{errors.comment}
}
{serverError && (
diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/SickLeaveTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/SickLeaveTab.tsx
index 180e6ae..8ac6d6f 100644
--- a/src/ui/pages/LeaveAndAdjustments/tabs/SickLeaveTab.tsx
+++ b/src/ui/pages/LeaveAndAdjustments/tabs/SickLeaveTab.tsx
@@ -3,6 +3,7 @@ import { quillModules } from "../../../utils/quillConfig";
import ReactQuill from "react-quill-new";
import Quill from "quill";
import "quill/dist/quill.snow.css"; // correct CSS
+import { useNavigate } from 'react-router-dom';
interface LeaveFormData {
fromDate: string;
@@ -22,6 +23,7 @@ const SickLeaveTab: React.FC<{
const [serverError, setServerError] = useState("");
const quillRef = useRef
(null);
const [sickErrors, setSickErrors] = useState>({});
+ const navigate = useNavigate()
useEffect(() => {
const editor = quillRef.current?.getEditor();
@@ -188,7 +190,8 @@ const SickLeaveTab: React.FC<{
if (result.success) {
resetForm();
- alert("SICK submitted!");
+ alert("SICK leave applied!");
+ navigate('/dashboard/leave-adjustments')
} else {
setServerError(result.message || "Something went wrong");
}
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 && (
+
+
+
+
+ | # |
+ Status |
+ Date |
+ Total Hours |
+ Total Pay |
+ Action |
+
+
+
+
+ {rows.map((row, index) => (
+
+ | {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..759ebd8 100644
--- a/src/ui/pages/Schedule/Schedule.tsx
+++ b/src/ui/pages/Schedule/Schedule.tsx
@@ -1,70 +1,147 @@
+import React, { useEffect, useState } from "react";
+import { useOutletContext } from "react-router-dom";
+import { TrackingState } from "../../layout/DashboardLayout";
+import { RefreshCw, Loader2 } from "lucide-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 { isTracking } = useOutletContext();
+
+ const [rows, setRows] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [refreshing, setRefreshing] = useState(false);
+ const [error, setError] = useState(null);
+ // const [lastUpdated, setLastUpdated] = useState(null);
+ const [lastRefreshed, setLastRefreshed] = useState(null);
+
+ /** Format: Only Date */
+ const formatOnlyDate = (input: string | null) => {
+ if (!input) return "-";
+ return DateTime.fromISO(input).toLocaleString(DateTime.DATE_MED);
+ };
+
+ /** Format: Date + Time */
+ const formatDateTime = (input: string | null) => {
+ if (!input) return "-";
+ return DateTime.fromISO(input).toLocaleString(DateTime.DATETIME_MED);
+ };
+
+ /** Fetch Data */
+ const fetchSchedules = async () => {
+ setRefreshing(true);
+ setLoading(true);
+ try {
+ const res = await window.electron.getSchedules();
+
+ if (!res.success) {
+ setError(res.message || "Failed to load schedules");
+ return;
+ }
+
+ setRows(res.result || []);
+ setLastRefreshed(new Date());
+ } catch (err: any) {
+ setError(err.message || "Unexpected error");
+ } finally {
+ setLoading(false);
+ setRefreshing(false);
}
- ];
+ };
+
+ /** Refresh Button */
+ const handleRefresh = async () => {
+
+ await fetchSchedules();
+ };
+
+ useEffect(() => {
+ fetchSchedules();
+ }, []);
+
+ /** Determine Active 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;
+ };
+
+ const activeSchedule = rows.find((r) => isActiveRow(r));
+
+ /** Skeleton Rows */
+ const renderSkeletonRows = () =>
+ Array.from({ length: 5 }).map((_, idx) => (
+
+ |
+ |
+ |
+ |
+
+ ));
+
+ const formatLastRefreshed = () => {
+ if (!lastRefreshed) return "Never";
+
+ return DateTime.fromJSDate(lastRefreshed)
+ .toLocal()
+ .toFormat("HH:mm:ss"); // Only time
+ };
return (
- <>
-
- {/* Header */}
-
-
Schedules
+
+ {/* HEADER */}
+
+
Schedules
+
+
+ {activeSchedule && (
+
+ Pay Date: {formatOnlyDate(activeSchedule.ScheduleDate)}
+
+ )}
+
+
-
-
- {/* โญ FIXED: Horizontal scroll wrapper */}
-
-
+ {/* TABLE WRAPPER */}
+
+ {/* Loading */}
+ {loading ? (
+
+ {renderSkeletonRows()}
+
+ ) : error ? (
+
{error}
+ ) : rows.length === 0 ? (
+
+ No schedules found
+
+ ) : (
+
+
| # |
@@ -76,24 +153,61 @@ 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)} |
))}
+
+ {refreshing && (
+
+ |
+
+ Refreshing...
+ |
+
+ )}
+ )}
+
+ {/* FOOTER */}
+
+
{rows.length} schedules found
+
+
+
+ {lastRefreshed &&
+ `Last updated: ${formatLastRefreshed()}`}
+
- >
+
);
};
diff --git a/src/ui/pages/Settings/SettingModal/ChangePaymentModal.tsx b/src/ui/pages/Settings/SettingModal/ChangePaymentModal.tsx
new file mode 100644
index 0000000..6c08447
--- /dev/null
+++ b/src/ui/pages/Settings/SettingModal/ChangePaymentModal.tsx
@@ -0,0 +1,157 @@
+import React, { useState, useEffect } from "react";
+import { CreditCard } from "lucide-react";
+import { useSelector, useDispatch } from "react-redux";
+import { RootState } from "../../../store";
+
+interface ChangePaymentModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+const ChangePaymentModal: React.FC = ({
+ isOpen,
+ onClose,
+}) => {
+ const dispatch = useDispatch();
+ const { profile } = useSelector((state: RootState) => state.profile);
+
+ const [method, setMethod] = useState(profile?.paymentMethod || "");
+ const [wiseEmail, setWiseEmail] = useState(profile?.wiseEmail || "");
+ const [veemAccount, setVeemAccount] = useState(profile?.veemAccount || "");
+
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState("");
+
+ useEffect(() => {
+ if (isOpen) {
+ setMethod(profile?.paymentMethod || "");
+ setWiseEmail(profile?.wiseEmail || "");
+ setVeemAccount(profile?.veemAccount || "");
+ setError("");
+ }
+ }, [isOpen]);
+
+ if (!isOpen) return null;
+
+ const handleSubmit = async () => {
+ setError("");
+
+ // Validation
+ if (!method) {
+ return setError("Please select a payment method.");
+ }
+
+ if (method === "wise" && !wiseEmail.trim()) {
+ return setError("Wise email is required.");
+ }
+
+ if (method === "wise" && !/^\S+@\S+\.\S+$/.test(wiseEmail)) {
+ return setError("Enter a valid Wise email.");
+ }
+
+ if (method === "veem" && !veemAccount.trim()) {
+ return setError("Veem account number is required.");
+ }
+
+ setLoading(true);
+
+ // try {
+ // // Replace with your electron API
+ // await window.electron.updatePayment({
+ // method,
+ // wiseEmail: method === "wise" ? wiseEmail.trim() : "",
+ // veemAccount: method === "veem" ? veemAccount.trim() : "",
+ // });
+
+ // // Refresh or update Redux
+ // // dispatch(updateProfile({...}));
+
+ // onClose();
+ // } catch (err) {
+ // setError("Failed to update payment info. Try again.");
+ // }
+
+ setLoading(false);
+ };
+
+ return (
+
+ );
+};
+
+export default ChangePaymentModal;
diff --git a/src/ui/pages/Settings/Settings.tsx b/src/ui/pages/Settings/Settings.tsx
index 7a0d6b1..c8a8f4a 100644
--- a/src/ui/pages/Settings/Settings.tsx
+++ b/src/ui/pages/Settings/Settings.tsx
@@ -1,13 +1,15 @@
import React, { useState } from "react";
-import { User, Mail, Phone, Calendar, MapPin, CreditCard, Globe, Lock } from "lucide-react";
+import { User, Mail, Phone, Calendar, MapPin, CreditCard, Globe, Lock, SquarePen } from "lucide-react";
import { useSelector } from "react-redux";
import { RootState } from "../../store";
import ChangePasswordModal from "./ChangePasswordModal";
+import ChangePaymentModal from "./SettingModal/ChangePaymentModal";
const Settings: React.FC = () => {
const { profile } = useSelector((state: RootState) => state.profile);
const [modalOpen, setModalOpen] = useState(false);
+ const [showPaymentModal,setShowPaymentModal] = useState(false)
const fullName = `${profile?.firstName || ""} ${profile?.lastName || ""}`.trim();
@@ -82,32 +84,33 @@ const Settings: React.FC = () => {
-
-
- ๐ฌ Skype
-
-
- {profile?.skype || "Not Linked"}
-
-
{/* Payment Info */}
-
-
- Payment
-
-
- {profile?.paymentMethod || "N/A"}
-
+
setShowPaymentModal(true)}>
+
+
+
+ Payment
+
+
+ {profile?.paymentMethod || "N/A"}
+
+
+
+
+
+
- {profile?.paymentMethod === "payoneer" && (
+
+ {profile?.paymentMethod === "wise" && (
- ๐ณ Payoneer
+ ๐ณ Wise
- {maskLast5(profile?.payoneerAccount)}
+ {maskLast5(profile?.wiseEmail)}
)}
@@ -137,8 +140,9 @@ const Settings: React.FC = () => {
- {/* Change Password Modal */}
+ {/* Modal */}
setModalOpen(false)} />
+ setShowPaymentModal(false)} />
);
};
diff --git a/src/ui/pages/Timesheets/ProjectLogsModal.tsx b/src/ui/pages/Timesheets/ProjectLogsModal.tsx
index 9724311..7144932 100644
--- a/src/ui/pages/Timesheets/ProjectLogsModal.tsx
+++ b/src/ui/pages/Timesheets/ProjectLogsModal.tsx
@@ -95,3 +95,4 @@ const ProjectLogsModal: React.FC = ({ isOpen, onClose, projectName, logs,
};
export default ProjectLogsModal;
+
diff --git a/src/ui/pages/Timesheets/Timesheets.tsx b/src/ui/pages/Timesheets/Timesheets.tsx
index 51bc4b0..b6cc269 100644
--- a/src/ui/pages/Timesheets/Timesheets.tsx
+++ b/src/ui/pages/Timesheets/Timesheets.tsx
@@ -1,8 +1,22 @@
import React, { useEffect, useState } from "react";
-import { CalendarDays, Clock, FileText, CheckCircle, XCircle, Eye, EyeOff } from "lucide-react";
+import {
+ CalendarDays,
+ Clock,
+ FileText,
+ CheckCircle,
+ XCircle,
+ Eye,
+ EyeOff,
+ RefreshCw,
+ Loader2
+} from "lucide-react";
import { useSelector } from "react-redux";
import { RootState } from "../../store";
import ProjectLogsModal from "./ProjectLogsModal";
+import { TrackingState } from "../../layout/DashboardLayout";
+import { useOutletContext } from "react-router-dom";
+import { DateTime } from "luxon";
+
interface TimesheetEntry {
id: number;
@@ -28,48 +42,83 @@ interface Project {
totalWorkTime: string;
totalWorkMs: number;
totalLogs: number;
- status:string
+ status: string
}
const Timesheets: React.FC = () => {
+ const { isTracking } = useOutletContext();
const totalHours = timesheetData.reduce((acc, t) => acc + t.hours, 0);
const approvedCount = timesheetData.filter((t) => t.status === "Approved").length;
const { windowLogs } = useSelector((state: RootState) => state.window);
- console.log("๐งพ Timesheet in component:", windowLogs);
+ console.log(" Timesheet in component:", windowLogs);
const [projectsData, setProjectsData] = useState([]);
const [logs, setLogs] = useState([]);
const [selectedProject, setSelectedProject] = useState(null);
const [selectedProjectName, setSelectedProjectName] = useState("");
const [modalOpen, setModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
+ const [refreshing, setRefreshing] = useState(false);
+ const [lastRefreshed, setLastRefreshed] = useState(null);
+ const [initialLoading, setInitialLoading] = useState(true);
+ const [tick, setTick] = useState(0);
+
+ const activeProjects = projectsData.filter(p => p.status === "ACTIVE");
+
useEffect(() => {
- const fetchProjects = async () => {
- try {
- console.log("๐ก Fetching projects...");
- const res = await window.electron.getProjectList();
- console.log("โ
Project list response:", res);
+ const interval = setInterval(() => {
+ setTick((t) => t + 1); // force re-render every second
+ }, 1000);
- if (res?.success && Array.isArray(res.data)) {
- // Store project list
- setProjectsData(res.data);
- } else {
- console.warn(" Unexpected response:", res);
- }
- } catch (err) {
- console.error("Error fetching projects:", err);
+ return () => clearInterval(interval);
+ }, []);
+
+ const formatLastRefreshed = () => {
+ if (!lastRefreshed) return "Never";
+
+ return DateTime.fromJSDate(lastRefreshed)
+ .toLocal()
+ .toFormat("HH:mm:ss"); // Only time
+ };
+
+
+
+ const fetchProjects = async () => {
+ setInitialLoading(true);
+ try {
+ console.log("๐ก Fetching projects...");
+ const res = await window.electron.getProjectList();
+ console.log("โ
Project list response:", res);
+
+ if (res?.success && Array.isArray(res.data)) {
+ // Store project list
+ setProjectsData(res.data);
+ setLastRefreshed(new Date());
+ } else {
+ console.warn(" Unexpected response:", res);
}
- };
+ } catch (err) {
+ console.error("Error fetching projects:", err);
+ } finally {
+ setInitialLoading(false);
+ setRefreshing(false);
+ }
+ };
+ useEffect(() => {
fetchProjects();
}, []);
-
+ const handleRefresh = async () => {
+ setRefreshing(true);
+ await fetchProjects();
+ };
const handleViewLogs = async (projectID: string, projectName: string) => {
try {
+ setModalOpen(true);
setLoading(true);
setSelectedProject(projectID);
setSelectedProjectName(projectName);
@@ -85,11 +134,13 @@ const Timesheets: React.FC = () => {
setLogs([]);
}
- setModalOpen(true);
+
} catch (error) {
console.error("Error fetching logs:", error);
+ setModalOpen(false)
} finally {
setLoading(false);
+
}
};
@@ -105,54 +156,49 @@ const Timesheets: React.FC = () => {
+
+
+ // Skeleton loader rows
+ const renderSkeletonRows = () => {
+ return Array.from({ length: 5 }).map((_, index) => (
+
+ |
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ ));
+ };
+
return (
-
+
{/* Header */}
-
-
-
Timesheets
-
+
+
Timesheets
- {/* Summary Cards */}
- {/*
-
-
-
-
Total Logged Hours
-
{totalHours} hrs
-
-
-
-
-
-
-
-
-
Approved Days
-
{approvedCount}
-
-
-
-
-
-
-
-
-
Pending Days
-
- {timesheetData.length - approvedCount}
-
-
-
-
-
-
*/}
-
{/* Timesheet Table */}
-
-
-
+
+
+
| # |
Project |
@@ -161,34 +207,83 @@ const Timesheets: React.FC = () => {
- {projectsData.map((entry, index) => (
-
- | {index + 1} |
- {entry.projectName} |
- {formatWorkTime(entry.totalWorkTime)} |
-
-
+ {initialLoading ? (
+ // Show skeleton while initially loading
+ renderSkeletonRows()
+ ) : refreshing && projectsData.length === 0 ? (
+ // Show skeleton while refreshing with empty data
+ renderSkeletonRows()
+ ) : (
+ // Show actual data
+ projectsData.map((entry, index) => (
+ |
+ | {index + 1} |
+ {entry.projectName} |
+ {formatWorkTime(entry.totalWorkTime)} |
+
+
+ |
+
+ ))
+ )}
+
+ {/* {refreshing && projectsData.length > 0 && (
+ // Show a single skeleton row at the top while refreshing with existing data
+
+ |
+
+
+ Refreshing...
+
|
- ))}
+ )} */}
{/* Footer Summary */}
-
- {/*
- Showing {timesheetData.length} entries
- */}
-
- Last updated: {new Date().toLocaleDateString()}
-
+
+
+ {activeProjects.length} project{activeProjects.length !== 1 ? 's' : ''} shown
+
+
+
+ {lastRefreshed && (
+
+ Last updated: {formatLastRefreshed()}
+
+ )}
+
+
+
+
{
logs={logs}
loading={loading}
/>
-
+
);
};
-export default Timesheets;
+export default Timesheets;
\ No newline at end of file
diff --git a/src/ui/store/profileSlice.tsx b/src/ui/store/profileSlice.tsx
index eca87d4..35f250d 100644
--- a/src/ui/store/profileSlice.tsx
+++ b/src/ui/store/profileSlice.tsx
@@ -19,6 +19,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
paymentMethod:string;
payoneerAccount:string;
veemAccount:string;
+ wiseEmail:string
_id: string;
}
diff --git a/types.d.ts b/types.d.ts
index 1ec1280..51df381 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -83,6 +83,7 @@ interface ProfileData {
paymentMethod: string;
payoneerAccount: string;
veemAccount: string;
+ wiseEmail: string
_id: string;
}
@@ -92,6 +93,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 +205,25 @@ interface Window {
payload: LeaveRequestPayload
) => Promise;
-
+ getSchedules: () => Promise;
+
+ getPayslips: (vaId: string) => Promise<{
+ success: boolean;
+ data?: PayslipResponse[];
+ message?: string;
+ }>;
+
+ getLeaveList: () => Promise<{
+ success: boolean;
+ data?: any;
+ }>;
+
+ getAdjustmentsList: (vaId: string) => Promise<{
+ success: boolean;
+ data?: any;
+ }>;
};
-
+
}