+
{rows.length} schedules found
+
+
+
+ {lastRefreshed &&
+ `Last updated: ${formatLastRefreshed()}`}
+
+
+
+
);
};
diff --git a/src/ui/pages/Settings/Settings.tsx b/src/ui/pages/Settings/Settings.tsx
index 7a0d6b1..180d786 100644
--- a/src/ui/pages/Settings/Settings.tsx
+++ b/src/ui/pages/Settings/Settings.tsx
@@ -82,14 +82,6 @@ const Settings: React.FC = () => {
-
diff --git a/src/ui/pages/Timesheets/Timesheets.tsx b/src/ui/pages/Timesheets/Timesheets.tsx
index 943104d..33d9d00 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,45 +42,78 @@ 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 () => {
+ 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 {
@@ -105,54 +152,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 +203,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/types.d.ts b/types.d.ts
index bb7af91..062dd79 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -211,6 +211,11 @@ interface Window {
data?: PayslipResponse[];
message?: string;
}>;
+
+ getLeaveList: () => Promise<{
+ success: boolean;
+ data?: any;
+ }>;
};
}