507 lines
14 KiB
TypeScript
507 lines
14 KiB
TypeScript
import React, { useState, useEffect, useRef } from "react";
|
|
import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react";
|
|
|
|
const projectsData = [
|
|
{
|
|
id: 1,
|
|
projectName: "Employee Tracker",
|
|
clientName: "TechCorp Pvt Ltd",
|
|
status: "Active",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 2,
|
|
projectName: "Sales Dashboard",
|
|
clientName: "BlueEdge Analytics",
|
|
status: "Pending",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 3,
|
|
projectName: "Inventory System",
|
|
clientName: "LogiFlow Inc",
|
|
status: "Completed",
|
|
action: "View Report",
|
|
},
|
|
{
|
|
id: 4,
|
|
projectName: "CRM Upgrade",
|
|
clientName: "NextWave Solutions",
|
|
status: "On Hold",
|
|
action: "Resume",
|
|
},
|
|
{
|
|
id: 5,
|
|
projectName: "Mobile App Redesign",
|
|
clientName: "PixelFusion",
|
|
status: "Active",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 6,
|
|
projectName: "E-Commerce Backend",
|
|
clientName: "ShopEasy",
|
|
status: "Pending",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 7,
|
|
projectName: "Finance Tracker",
|
|
clientName: "MoneyWise",
|
|
status: "Active",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 8,
|
|
projectName: "Helpdesk Portal",
|
|
clientName: "ServePro",
|
|
status: "Completed",
|
|
action: "View Report",
|
|
},
|
|
{
|
|
id: 9,
|
|
projectName: "Marketing Automation",
|
|
clientName: "AdBoost",
|
|
status: "Active",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 10,
|
|
projectName: "Data Pipeline",
|
|
clientName: "CloudWave",
|
|
status: "On Hold",
|
|
action: "Resume",
|
|
},
|
|
{
|
|
id: 11,
|
|
projectName: "Support Chatbot",
|
|
clientName: "SmartServe",
|
|
status: "Active",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 12,
|
|
projectName: "Payroll System",
|
|
clientName: "PeopleFirst HR",
|
|
status: "Pending",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 13,
|
|
projectName: "IoT Dashboard",
|
|
clientName: "SensorGrid",
|
|
status: "Completed",
|
|
action: "View Report",
|
|
},
|
|
{
|
|
id: 14,
|
|
projectName: "Security Portal",
|
|
clientName: "SafeNet",
|
|
status: "Active",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 15,
|
|
projectName: "Social Media Tool",
|
|
clientName: "Buzzify",
|
|
status: "Pending",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 16,
|
|
projectName: "AI Model Trainer",
|
|
clientName: "NeuroTech",
|
|
status: "On Hold",
|
|
action: "Resume",
|
|
},
|
|
{
|
|
id: 17,
|
|
projectName: "Project Tracker",
|
|
clientName: "TaskFlow",
|
|
status: "Active",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 18,
|
|
projectName: "Booking Portal",
|
|
clientName: "GoTravel",
|
|
status: "Completed",
|
|
action: "View Report",
|
|
},
|
|
{
|
|
id: 19,
|
|
projectName: "Time Logger",
|
|
clientName: "Clockify Pro",
|
|
status: "Active",
|
|
action: "Start Tracking",
|
|
},
|
|
{
|
|
id: 20,
|
|
projectName: "Analytics Engine",
|
|
clientName: "DataStream",
|
|
status: "Pending",
|
|
action: "Start Tracking",
|
|
},
|
|
];
|
|
|
|
interface WidgetProps {
|
|
title: string;
|
|
value: string | number;
|
|
subtitle?: string;
|
|
icon?: React.ReactNode;
|
|
trend?: number;
|
|
color?: "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info";
|
|
}
|
|
|
|
interface Project {
|
|
id: number;
|
|
projectName: string;
|
|
clientName: string;
|
|
}
|
|
|
|
// Widget Component
|
|
const Widget: React.FC<WidgetProps> = ({
|
|
title,
|
|
value,
|
|
subtitle,
|
|
icon,
|
|
trend,
|
|
color = "primary",
|
|
}) => {
|
|
return (
|
|
<div className="card bg-base-100 shadow-sm hover:shadow-lg transition-shadow">
|
|
<div className="card-body">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<h3 className=" font-medium text-base-content/60">{title}</h3>
|
|
<p className="text-sm font-bold mt-2">{value}</p>
|
|
</div>
|
|
<div className={`p-3 bg-${color}/10 rounded-lg`}>
|
|
<div className={`text-${color}`}>{icon}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
|
|
// Main Dashboard Component
|
|
const Dashboard: React.FC = () => {
|
|
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
|
const [isTracking, setIsTracking] = useState(false);
|
|
const [isPaused, setIsPaused] = useState(false);
|
|
const [elapsedTime, setElapsedTime] = useState(0); // in seconds
|
|
const [startTime, setStartTime] = useState<Date | null>(null);
|
|
const [endTime, setEndTime] = useState<Date | null>(null);
|
|
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
// Load cached project or auto-select single project
|
|
useEffect(() => {
|
|
const cachedProject = localStorage.getItem("activeProject");
|
|
if (cachedProject) {
|
|
const parsed = JSON.parse(cachedProject) as Project;
|
|
setSelectedProject(parsed);
|
|
} else if (projectsData.length === 1) {
|
|
setSelectedProject(projectsData[0]);
|
|
}
|
|
}, []);
|
|
|
|
// Format elapsed time (hh:mm:ss)
|
|
const formatTime = (seconds: number) => {
|
|
const h = Math.floor(seconds / 3600);
|
|
const m = Math.floor((seconds % 3600) / 60);
|
|
const s = seconds % 60;
|
|
return `${h}h ${m}m ${s}s`;
|
|
};
|
|
|
|
// Start tracking manually
|
|
const handleStartTracking = () => {
|
|
if (!selectedProject) return;
|
|
|
|
setIsTracking(true);
|
|
setIsPaused(false);
|
|
setElapsedTime(0);
|
|
setStartTime(new Date());
|
|
setEndTime(null);
|
|
|
|
// Cache selected project
|
|
localStorage.setItem("activeProject", JSON.stringify(selectedProject));
|
|
};
|
|
|
|
// ⏱ Timer logic
|
|
useEffect(() => {
|
|
if (isTracking && !isPaused) {
|
|
timerRef.current = setInterval(() => {
|
|
setElapsedTime((prev) => prev + 1);
|
|
}, 1000);
|
|
} else if (timerRef.current) {
|
|
clearInterval(timerRef.current);
|
|
}
|
|
|
|
return () => {
|
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
};
|
|
}, [isTracking, isPaused]);
|
|
|
|
// ⏸ Pause/Resume button handler
|
|
const handlePauseResume = () => {
|
|
setIsPaused((prev) => !prev);
|
|
};
|
|
|
|
// Stop tracking
|
|
const handleStopTracking = () => {
|
|
if (!selectedProject) return;
|
|
|
|
setIsTracking(false);
|
|
setIsPaused(false);
|
|
setEndTime(new Date());
|
|
|
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
|
|
// Keep project in view, just stop tracking.
|
|
// Remove tracking state but keep the selected project cached.
|
|
const summary = {
|
|
project: selectedProject.projectName,
|
|
totalTime: formatTime(elapsedTime),
|
|
start: startTime?.toLocaleTimeString(),
|
|
end: new Date().toLocaleTimeString(),
|
|
};
|
|
|
|
console.log("✅ Final Tracking Summary:", summary);
|
|
|
|
// Save the stopped state for reference
|
|
localStorage.setItem(
|
|
"activeProject",
|
|
JSON.stringify({
|
|
...selectedProject,
|
|
lastSession: summary,
|
|
})
|
|
);
|
|
|
|
// Reset timer values but keep selected project
|
|
setElapsedTime(0);
|
|
};
|
|
|
|
// 🧾 When user picks a project manually
|
|
const handleProjectSelect = (id: number) => {
|
|
if (isTracking) return;
|
|
const project = projectsData.find((item) => item.id === id);
|
|
if (project) {
|
|
setSelectedProject(project);
|
|
localStorage.setItem("activeProject", JSON.stringify(project));
|
|
}
|
|
};
|
|
|
|
return (
|
|
<main className="flex-1 overflow-y-auto md:px-0.5 md:py-0 no-scrollbar">
|
|
{/* Welcome Section */}
|
|
<div className="mb-4">
|
|
<h1 className="text-lg font-bold">Dashboard</h1>
|
|
|
|
<button
|
|
onClick={async () => {
|
|
await window.electron.uploadScreenshot();
|
|
}}
|
|
>
|
|
ScreenShot Test
|
|
</button>
|
|
</div>
|
|
|
|
{/* Widgets Grid */}
|
|
<div className="mb-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3 gap-4 ">
|
|
<Widget
|
|
title="Logged Hours Today"
|
|
value="6h 24m"
|
|
subtitle="Today's total time"
|
|
icon={<Clock size={24} />}
|
|
trend={12}
|
|
color="primary"
|
|
/>
|
|
<Widget
|
|
title="Logged Hours This Week"
|
|
value="4h 48m"
|
|
subtitle="75% productivity"
|
|
icon={<TrendingUp size={24} />}
|
|
trend={8}
|
|
color="success"
|
|
/>
|
|
<Widget
|
|
title="Active Project"
|
|
value={selectedProject ? selectedProject.projectName : "—"}
|
|
subtitle="Until day ends"
|
|
icon={<ProjectorIcon size={24} />}
|
|
color="warning"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tracking Card */}
|
|
<div className="w-full mb-4">
|
|
<div className="text-md mb-2 font-bold flex justify-between items-center">
|
|
<span>Tracking</span>
|
|
{isTracking && (
|
|
<button
|
|
className="btn btn-xs btn-error rounded-full"
|
|
onClick={handleStopTracking}
|
|
>
|
|
<Square className="w-3 h-3" />
|
|
Stop Tracking
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="card bg-base-100 shadow-sm border border-base-200 transition-all duration-300 w-full">
|
|
<div className="card-body">
|
|
{selectedProject ? (
|
|
<>
|
|
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
|
<div className="text-center sm:text-left">
|
|
<p className="text-sm font-extrabold text-success tracking-tight">
|
|
{formatTime(elapsedTime)}
|
|
</p>
|
|
<p className="text-xs text-base-content/70 mt-1">
|
|
{isTracking ? "Current Session" : "Ready to Start"}
|
|
</p>
|
|
</div>
|
|
|
|
{!isTracking ? (
|
|
<button
|
|
onClick={handleStartTracking}
|
|
className="btn btn-sm btn-success rounded-xl hover:scale-105 transition-transform duration-200 w-full sm:w-auto"
|
|
>
|
|
<Play className="w-3 h-3" />
|
|
Start Tracking
|
|
</button>
|
|
) : (
|
|
<button
|
|
onClick={handlePauseResume}
|
|
className={`btn btn-sm rounded-xl transition-transform duration-200 w-full sm:w-auto ${isPaused ? "btn-primary" : "btn-warning"
|
|
}`}
|
|
>
|
|
{isPaused ? (
|
|
<>
|
|
<Play className="w-3 h-3" />
|
|
Resume
|
|
</>
|
|
) : (
|
|
<>
|
|
<Pause className="w-3 h-3" />
|
|
Pause
|
|
</>
|
|
)}
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="divider my-2"></div>
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 text-sm">
|
|
{/* Project Name */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
|
<span className="text-base-content/70">Project:</span>
|
|
<span className="font-semibold text-base-content truncate">
|
|
{selectedProject.projectName}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Client Name */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
|
<span className="text-base-content/70">Client:</span>
|
|
<span className="truncate">{selectedProject.clientName}</span>
|
|
</div>
|
|
|
|
{/* Status */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
|
<span className="text-base-content/70">Status:</span>
|
|
<span
|
|
className={`badge ${isPaused
|
|
? "badge-warning"
|
|
: isTracking
|
|
? "badge-success"
|
|
: "badge-neutral"
|
|
}`}
|
|
>
|
|
{isPaused ? "Paused" : isTracking ? "Active" : "Ready"}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Start Time */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
|
<span className="text-base-content/70">Start Time:</span>
|
|
<span>{startTime?.toLocaleTimeString() || "—"}</span>
|
|
</div>
|
|
</div>
|
|
|
|
</>
|
|
) : (
|
|
<p className="text-sm text-base-content/60 text-center">
|
|
Select a project to start tracking.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Projects Table */}
|
|
<div>
|
|
<p className="text-md mb-2 font-bold">Projects</p>
|
|
<div
|
|
className="overflow-y-auto no-scrollbar overflow-x-auto rounded-box border border-base-content/10 bg-base-100 shadow-md"
|
|
style={{ minHeight: "100px", maxHeight: "30vh" }}
|
|
>
|
|
<table className="table table-zebra w-full text-sm">
|
|
<thead className="sticky top-0 bg-base-200 z-10">
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Project Name</th>
|
|
<th>Client Name</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{projectsData.map((project) => (
|
|
<tr key={project.id} className="align-middle">
|
|
<th>{project.id}</th>
|
|
<td>{project.projectName}</td>
|
|
<td>{project.clientName}</td>
|
|
<td>
|
|
<div
|
|
className={`badge badge-sm whitespace-nowrap ${project.status === "Active"
|
|
? "badge-success"
|
|
: project.status === "Pending"
|
|
? "badge-warning"
|
|
: project.status === "Completed"
|
|
? "badge-info"
|
|
: "badge-neutral"
|
|
}`}
|
|
>
|
|
{project.status}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div
|
|
className={`badge badge-sm whitespace-nowrap ${project.action === "View Report"
|
|
? "badge-info"
|
|
: project.action === "Resume"
|
|
? "badge-secondary"
|
|
: "badge-primary"
|
|
}`}
|
|
>
|
|
Select
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
};
|
|
|
|
export default Dashboard;
|