refresh dashboard projects
This commit is contained in:
parent
4fb0720e7d
commit
17dcdec1c6
|
|
@ -113,8 +113,8 @@ const DashboardLayout = () => {
|
||||||
{ label: "Dashboard", path: "/dashboard", icon: <Home size={18} />, exact: true },
|
{ label: "Dashboard", path: "/dashboard", icon: <Home size={18} />, exact: true },
|
||||||
{ label: "Timesheet", path: "/dashboard/timesheet", icon: <TimerIcon size={18} /> },
|
{ label: "Timesheet", path: "/dashboard/timesheet", icon: <TimerIcon size={18} /> },
|
||||||
{ label: "Leave & Adjust", path: "/dashboard/leave-adjustments", icon: <CalendarDays size={18} /> },
|
{ label: "Leave & Adjust", path: "/dashboard/leave-adjustments", icon: <CalendarDays size={18} /> },
|
||||||
{ label: "Schedule", path: "/dashboard/schedule", icon: <ClipboardClock size={18} />},
|
{ label: "Schedule", path: "/dashboard/schedule", icon: <ClipboardClock size={18} /> },
|
||||||
{ label: "Pays Slips", path: "/dashboard/pays-slips", icon: <CircleDollarSign size={18} />},
|
{ label: "Pays Slips", path: "/dashboard/pays-slips", icon: <CircleDollarSign size={18} /> },
|
||||||
{ label: "Settings", path: "/dashboard/settings", icon: <SettingsIcon size={18} /> },
|
{ label: "Settings", path: "/dashboard/settings", icon: <SettingsIcon size={18} /> },
|
||||||
{ label: "VPN", path: "/dashboard/vpn", icon: <GlobeLock size={18} /> },
|
{ label: "VPN", path: "/dashboard/vpn", icon: <GlobeLock size={18} /> },
|
||||||
{ label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> },
|
{ label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> },
|
||||||
|
|
@ -630,170 +630,171 @@ const DashboardLayout = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Header */}
|
<div className="flex flex-col h-screen overflow-hidden">
|
||||||
{isTracking &&
|
{/* Header */}
|
||||||
<header className="w-full flex justify-between items-center h-12 px-6 bg-base-100 border-b border-base-300 sticky top-0 z-30">
|
{isTracking &&
|
||||||
<div className="flex items-center space-x-3">
|
<header className="w-full flex justify-between items-center px-4 py-2 bg-base-100 border-b border-base-300 sticky top-0 z-30">
|
||||||
{isStopping ? (
|
<div className="flex items-center space-x-3">
|
||||||
// Skeleton for left side during stop
|
{isStopping ? (
|
||||||
<div className="flex items-center space-x-3">
|
// Skeleton for left side during stop
|
||||||
<div className="skeleton w-8 h-8 rounded-full"></div>
|
<div className="flex items-center space-x-3">
|
||||||
<div className="skeleton w-32 h-4"></div>
|
<div className="skeleton w-8 h-8 rounded-full"></div>
|
||||||
</div>
|
<div className="skeleton w-32 h-4"></div>
|
||||||
) : selectedProject ? (
|
</div>
|
||||||
isTracking ? (
|
) : selectedProject ? (
|
||||||
<button
|
isTracking ? (
|
||||||
className="bg-error text-white p-1.5 rounded-full flex items-center justify-center cursor-pointer"
|
<button
|
||||||
onClick={handleStopTracking}
|
className="bg-error text-white p-1.5 rounded-full flex items-center justify-center cursor-pointer"
|
||||||
disabled={loadingStop}
|
onClick={handleStopTracking}
|
||||||
title="Stop Tracking"
|
disabled={loadingStop}
|
||||||
>
|
title="Stop Tracking"
|
||||||
{loadingStop ? (
|
>
|
||||||
<span className="loading loading-spinner loading-xs text-white"></span>
|
{loadingStop ? (
|
||||||
) : (
|
<span className="loading loading-spinner loading-xs text-white"></span>
|
||||||
<Pause size={16} />
|
) : (
|
||||||
)}
|
<Pause size={16} />
|
||||||
</button>
|
)}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className="bg-success text-white p-1.5 rounded-full flex items-center justify-center cursor-pointer"
|
||||||
|
onClick={() => handleStartTracking(selectedProject)}
|
||||||
|
title="Start Tracking"
|
||||||
|
>
|
||||||
|
<Play size={16} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="bg-success text-white p-1.5 rounded-full flex items-center justify-center cursor-pointer"
|
className="bg-gray-400 text-white p-1.5 rounded-full flex items-center justify-center cursor-not-allowed"
|
||||||
onClick={() => handleStartTracking(selectedProject)}
|
disabled
|
||||||
title="Start Tracking"
|
title="Select a project to start"
|
||||||
>
|
>
|
||||||
<Play size={16} />
|
<Play size={16} />
|
||||||
</button>
|
</button>
|
||||||
)
|
)}
|
||||||
) : (
|
|
||||||
|
{!isStopping && selectedProject ? (
|
||||||
|
<span className="ml-2 text-sm text-base-content/70">
|
||||||
|
{selectedProject.projectName}
|
||||||
|
</span>
|
||||||
|
) : !isStopping ? (
|
||||||
|
<span className="ml-2 text-sm text-base-content/40 italic">
|
||||||
|
No Project Selected
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
{isStopping ? (
|
||||||
|
// Skeleton for right side during stop
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="skeleton w-20 h-6 rounded"></div>
|
||||||
|
<div className="skeleton w-6 h-6 rounded"></div>
|
||||||
|
<div className="skeleton w-8 h-8 rounded"></div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="badge badge-success text-white font-semibold px-3 py-2 shadow-sm">
|
||||||
|
{formatTime(
|
||||||
|
isTracking ? elapsedTime : parseTimeStringToSeconds(selectedProject?.totalWorkTime || "0h 0m 0s")
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <span className="text-error">
|
||||||
|
<ShieldBan size={18} />
|
||||||
|
</span> */}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (toggleWindowSize.current) {
|
||||||
|
window.electron.resizeWindow(500, 85);
|
||||||
|
toggleWindowSize.current = false;
|
||||||
|
} else {
|
||||||
|
window.electron.resizeWindow(500, 600);
|
||||||
|
toggleWindowSize.current = true;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="btn btn-xs btn-outline btn-primary"
|
||||||
|
>
|
||||||
|
{toggleWindowSize.current ? <ChevronDown /> : <ChevronUp />}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* Layout Body */}
|
||||||
|
<div className={`flex flex-1 bg-base-200 overflow-hidden`}>
|
||||||
|
{/* Sidebar */}
|
||||||
|
<aside className="flex-none w-16 border-r border-base-300 bg-base-100 flex flex-col items-center py-4 gap-2">
|
||||||
|
{menuItems.map((item) => (
|
||||||
|
<div className="tooltip z-100 tooltip-right " key={item.label} data-tip={item.label}>
|
||||||
|
<NavLink
|
||||||
|
to={item.path}
|
||||||
|
end={item.exact}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex items-center justify-center w-9 h-9 rounded-lg transition-all duration-150 cursor-pointer ${isActive
|
||||||
|
? "bg-primary text-primary-content"
|
||||||
|
: "hover:bg-base-300 text-base-content"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className={`absolute ${!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}
|
||||||
|
checked={theme === "dark"} />
|
||||||
|
|
||||||
|
{/* sun icon */}
|
||||||
|
<svg
|
||||||
|
className="swap-off h-5 w-5 fill-current"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
|
||||||
|
</svg>
|
||||||
|
{/* moon icon */}
|
||||||
|
<svg
|
||||||
|
className="swap-on h-5 w-5 fill-current"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isTracking && (
|
||||||
<button
|
<button
|
||||||
className="bg-gray-400 text-white p-1.5 rounded-full flex items-center justify-center cursor-not-allowed"
|
onClick={() => setShowLogoutModal(true)}
|
||||||
disabled
|
className="flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-150 hover:bg-error/10 text-error hover:text-error mt-auto cursor-pointer tooltip tooltip-right"
|
||||||
title="Select a project to start"
|
data-tip={"Logout"}
|
||||||
>
|
>
|
||||||
<Play size={16} />
|
<LogOut size={18} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
</aside>
|
||||||
|
|
||||||
{!isStopping && selectedProject ? (
|
{/* Main Content */}
|
||||||
<span className="ml-2 text-sm text-base-content/70">
|
<main className="flex-1 overflow-y-auto p-4 no-scrollbar bg-base-200">
|
||||||
{selectedProject.projectName}
|
<Outlet context={trackingState} />
|
||||||
</span>
|
</main>
|
||||||
) : !isStopping ? (
|
|
||||||
<span className="ml-2 text-sm text-base-content/40 italic">
|
|
||||||
No Project Selected
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
<LogoutModal
|
||||||
{isStopping ? (
|
open={showLogoutModal}
|
||||||
// Skeleton for right side during stop
|
onClose={() => setShowLogoutModal(false)}
|
||||||
<div className="flex items-center space-x-3">
|
onConfirm={handleLogout}
|
||||||
<div className="skeleton w-20 h-6 rounded"></div>
|
/>
|
||||||
<div className="skeleton w-6 h-6 rounded"></div>
|
|
||||||
<div className="skeleton w-8 h-8 rounded"></div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="badge badge-success text-white font-semibold px-3 py-2 shadow-sm">
|
|
||||||
{formatTime(
|
|
||||||
isTracking ? elapsedTime : parseTimeStringToSeconds(selectedProject?.totalWorkTime || "0h 0m 0s")
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className="text-error">
|
|
||||||
<ShieldBan size={18} />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
if (toggleWindowSize.current) {
|
|
||||||
window.electron.resizeWindow(500, 85);
|
|
||||||
toggleWindowSize.current = false;
|
|
||||||
} else {
|
|
||||||
window.electron.resizeWindow(500, 600);
|
|
||||||
toggleWindowSize.current = true;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="btn btn-xs btn-outline btn-primary"
|
|
||||||
>
|
|
||||||
{toggleWindowSize.current ? <ChevronDown /> : <ChevronUp />}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
}
|
|
||||||
|
|
||||||
{/* Layout Body */}
|
|
||||||
<div className={`flex ${!isTracking ? "max-h-screen min-h-screen" : "h-[calc(100vh-3rem)]"} bg-base-200 overflow-hidden`}>
|
|
||||||
{/* Sidebar */}
|
|
||||||
<aside className="w-20 bg-base-100 border-r border-base-300 flex flex-col items-center py-4 space-y-4 relative">
|
|
||||||
{menuItems.map((item) => (
|
|
||||||
<div className="tooltip z-100 tooltip-right" key={item.label} data-tip={item.label}>
|
|
||||||
<NavLink
|
|
||||||
to={item.path}
|
|
||||||
end={item.exact}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
`flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-150 cursor-pointer ${isActive
|
|
||||||
? "bg-primary text-primary-content"
|
|
||||||
: "hover:bg-base-300 text-base-content"
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item.icon}
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<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}
|
|
||||||
checked={theme === "dark"} />
|
|
||||||
|
|
||||||
{/* sun icon */}
|
|
||||||
<svg
|
|
||||||
className="swap-off h-5 w-5 fill-current"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
|
|
||||||
</svg>
|
|
||||||
{/* moon icon */}
|
|
||||||
<svg
|
|
||||||
className="swap-on h-5 w-5 fill-current"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!isTracking && (
|
|
||||||
<button
|
|
||||||
onClick={() => setShowLogoutModal(true)}
|
|
||||||
className="flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-150 hover:bg-error/10 text-error hover:text-error mt-auto cursor-pointer tooltip tooltip-right"
|
|
||||||
data-tip={"Logout"}
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
|
||||||
<main className="flex-1 overflow-y-auto p-4 no-scrollbar bg-base-200">
|
|
||||||
<Outlet context={trackingState} />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<LogoutModal
|
|
||||||
open={showLogoutModal}
|
|
||||||
onClose={() => setShowLogoutModal(false)}
|
|
||||||
onConfirm={handleLogout}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
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 { useOutletContext } from "react-router-dom";
|
||||||
import NoData from "./NoData";
|
import NoData from "./NoData";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
@ -105,6 +105,7 @@ const Dashboard: React.FC = () => {
|
||||||
const selectedProject = useSelector((state: any) => state.projects.selectedProject);
|
const selectedProject = useSelector((state: any) => state.projects.selectedProject);
|
||||||
const projectSummary = useSelector((state: any) => state.projects.projectSummary);
|
const projectSummary = useSelector((state: any) => state.projects.projectSummary);
|
||||||
const projectTrackingMap = useSelector((state: any) => state.projects.projectTrackingMap);
|
const projectTrackingMap = useSelector((state: any) => state.projects.projectTrackingMap);
|
||||||
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
|
||||||
console.log("Line 111", projectsData, selectedProject, projectTrackingMap)
|
console.log("Line 111", projectsData, selectedProject, projectTrackingMap)
|
||||||
|
|
||||||
|
|
@ -127,60 +128,66 @@ const Dashboard: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchProjects = async () => {
|
||||||
const fetchProjects = async () => {
|
try {
|
||||||
try {
|
console.log("📡 Fetching projects...Dashboard");
|
||||||
console.log("📡 Fetching projects...Dashboard");
|
const res = await window.electron.getProjectList();
|
||||||
const res = await window.electron.getProjectList();
|
console.log(" Project list response in dashboard:", res);
|
||||||
console.log(" Project list response in dashboard:", res);
|
|
||||||
|
|
||||||
if (res?.success && Array.isArray(res.data)) {
|
if (res?.success && Array.isArray(res.data)) {
|
||||||
// Store data redux store
|
// Store data redux store
|
||||||
dispatch(setProjectsDataSlice(res.data));
|
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(
|
dispatch(
|
||||||
setProjectSummarySlice({
|
initializeProjectTracking({
|
||||||
totalProjects: res.totalProjects || 0,
|
projectId: firstProject.projectId,
|
||||||
activeProjects: res.activeProjects || 0,
|
initialTime: initialElapsed,
|
||||||
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(
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
fetchProjects();
|
fetchProjects();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
setIsRefreshing(true);
|
||||||
|
await fetchProjects();
|
||||||
|
setTimeout(() => setIsRefreshing(false), 500); // Small delay for visual feedback
|
||||||
|
};
|
||||||
|
|
||||||
const selectedData = selectedProject
|
const selectedData = selectedProject
|
||||||
? projectTrackingMap[selectedProject.projectId]
|
? projectTrackingMap[selectedProject.projectId]
|
||||||
: null;
|
: null;
|
||||||
|
|
@ -234,7 +241,7 @@ const Dashboard: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex-1 overflow-hidden no-scrollbar max-h-screen">
|
<main className="flex-1 overflow-hidden no-scrollbar max-h-[85vh]">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<h1 className="text-md font-bold">Dashboard</h1>
|
<h1 className="text-md font-bold">Dashboard</h1>
|
||||||
|
|
@ -243,7 +250,7 @@ const Dashboard: React.FC = () => {
|
||||||
<div className="w-full mb-4">
|
<div className="w-full mb-4">
|
||||||
<div className="card bg-base-100 border border-base-200 shadow-sm card-sm">
|
<div className="card bg-base-100 border border-base-200 shadow-sm card-sm">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{isStopping ? (
|
{(isStopping || isRefreshing) ? (
|
||||||
// Skeleton loader for tracking section
|
// Skeleton loader for tracking section
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-3">
|
<div className="flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||||
<div className="text-center sm:text-left w-full">
|
<div className="text-center sm:text-left w-full">
|
||||||
|
|
@ -318,10 +325,19 @@ const Dashboard: React.FC = () => {
|
||||||
{/* 🔹 Sticky Header */}
|
{/* 🔹 Sticky Header */}
|
||||||
<div className="sticky top-0 z-20 bg-base-100 border-b border-base-content/10 p-3 flex justify-between items-center">
|
<div className="sticky top-0 z-20 bg-base-100 border-b border-base-content/10 p-3 flex justify-between items-center">
|
||||||
<p className="text-md font-bold">Projects</p>
|
<p className="text-md font-bold">Projects</p>
|
||||||
|
<button
|
||||||
|
onClick={handleRefresh}
|
||||||
|
disabled={isRefreshing}
|
||||||
|
className="btn btn-xs btn-ghost gap-1 text-warning"
|
||||||
|
title="Refresh projects"
|
||||||
|
>
|
||||||
|
<RefreshCw className={`w-3 h-3 text-warning ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 🔹 Table or Empty State or Skeleton */}
|
{/* 🔹 Table or Empty State or Skeleton */}
|
||||||
{isStopping ? (
|
{(isStopping || isRefreshing) ? (
|
||||||
// Skeleton loader for projects table
|
// Skeleton loader for projects table
|
||||||
<div className="p-4 space-y-3">
|
<div className="p-4 space-y-3">
|
||||||
{[...Array(3)].map((_, index) => (
|
{[...Array(3)].map((_, index) => (
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ const Timesheets: React.FC = () => {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 max-h-screen">
|
<div className="flex flex-col gap-2 max-h-screen ">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|
@ -150,7 +150,7 @@ const Timesheets: React.FC = () => {
|
||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
{/* Timesheet Table */}
|
{/* Timesheet Table */}
|
||||||
<div className="overflow-y-auto rounded-box border border-base-content/10 bg-base-100 shadow-md max-h-[80vh] min-h-[83vh]">
|
<div className="overflow-y-auto rounded-box border border-base-content/10 bg-base-100 shadow-md max-h-[80vh] min-h-[80vh]">
|
||||||
<table className="table table-zebra w-full text-[12px]">
|
<table className="table table-zebra w-full text-[12px]">
|
||||||
<thead className="sticky top-0 bg-base-200 z-10 ">
|
<thead className="sticky top-0 bg-base-200 z-10 ">
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue