pmtool / src /components /tasks /TaskTimeLogCard.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
8.19 kB
import React, { useState, useEffect } from "react";
import { TimeLog, timeLogApi } from "@/services/timeLogApi";
import { Employee, employeeApi } from "@/services/employeeApi";
import { format } from "date-fns";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { Clock, Loader2, User } from "lucide-react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { useQuery } from "@tanstack/react-query";
interface TaskTimeLogCardProps {
taskId: number;
entityType?: "Task" | "Issue";
}
const TaskTimeLogCard: React.FC<TaskTimeLogCardProps> = ({
taskId,
entityType = "Task"
}) => {
const [timeLogs, setTimeLogs] = useState<TimeLog[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Fetch employees data
const {
data: employees = [],
isLoading: isLoadingEmployees
} = useQuery({
queryKey: ["employees"],
queryFn: () => employeeApi.getAll(),
});
useEffect(() => {
const fetchTimeLogs = async () => {
try {
setIsLoading(true);
setError(null);
let logs: TimeLog[] = [];
if (entityType === "Issue") {
logs = await timeLogApi.getTimeLogByIssueID(taskId);
} else {
logs = await timeLogApi.getTimeLogByTaskID(taskId);
}
setTimeLogs(logs);
} catch (err) {
console.error(`Error fetching time logs for ${entityType}:`, err);
setError(`Failed to load time logs for this ${entityType.toLowerCase()}. Please try again later.`);
} finally {
setIsLoading(false);
}
};
if (taskId) {
fetchTimeLogs();
}
}, [taskId, entityType]);
// Format date for display
const formatDate = (dateString: string) => {
try {
return format(new Date(dateString), "MMM dd, yyyy");
} catch (error) {
return "Invalid date";
}
};
// Utility to strip HTML tags from text for tooltips
const stripHtmlTags = (html: string): string => {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
return tempDiv.textContent || tempDiv.innerText || '';
};
// Helper to get employee name by userId
const getEmployeeNameByUserId = (userId: string): string => {
const employee = employees.find(emp => emp.userId.toString() === userId);
if (employee) {
return `${employee.firstName} ${employee.lastName}`;
}
return userId;
};
// Calculate total hours spent
const totalHoursSpent = timeLogs.reduce((total, log) => total + log.hrsSpent, 0);
// Mobile view time log card
const renderMobileTimeLog = (log: TimeLog) => (
<div key={log.id} className="border rounded-md p-3 mb-2 text-sm">
<div className="flex justify-between items-start mb-2">
<Badge variant="outline" className="font-mono bg-indigo-50 text-indigo-700 border-indigo-200 px-2 py-0.5">
{log.startTime} - {log.endTime}
</Badge>
<Badge variant="secondary" className="font-medium">
{log.hrsSpent.toFixed(2)} hrs
</Badge>
</div>
<div className="mb-2">
<p className="text-xs text-muted-foreground mb-1">Date</p>
<div className="flex items-center">
<Clock className="h-3 w-3 text-muted-foreground mr-1" />
<span>{formatDate(log.workDate)}</span>
</div>
</div>
<div className="border-t pt-2 mt-2">
<p className="text-xs text-muted-foreground mb-1">Comments</p>
<div className="text-sm line-clamp-3" title={stripHtmlTags(log.comments || "No comments")} dangerouslySetInnerHTML={{ __html: log.comments || "No comments" }} />
</div>
<div className="text-xs text-muted-foreground mt-2 text-right">
<div className="flex items-center justify-end gap-1">
<Avatar className="h-5 w-5">
<AvatarFallback className="bg-primary/10 text-primary text-xs">
{getEmployeeNameByUserId(log.createdBy)?.slice(0, 2).toUpperCase() || 'U'}
</AvatarFallback>
</Avatar>
<span>Logged by {getEmployeeNameByUserId(log.createdBy)}</span>
</div>
</div>
</div>
);
return (
<Card className="border shadow-sm">
<CardHeader className="pb-2 sm:pb-4">
<CardTitle className="text-lg sm:text-xl flex items-center">
<Clock className="mr-2 h-4 w-4 sm:h-5 sm:w-5" />
Time Logs
</CardTitle>
<CardDescription>
Time spent on this {entityType.toLowerCase()}: {totalHoursSpent.toFixed(2)} hours
</CardDescription>
</CardHeader>
<CardContent>
{isLoading || isLoadingEmployees ? (
<div className="flex justify-center items-center py-6">
<Loader2 className="h-6 w-6 animate-spin text-primary" />
<span className="ml-2">Loading time logs...</span>
</div>
) : error ? (
<div className="text-center text-red-500 py-4">{error}</div>
) : timeLogs.length === 0 ? (
<div className="text-center text-muted-foreground py-6">
No time logs recorded for this {entityType.toLowerCase()}.
</div>
) : (
<>
{/* Mobile view - Card layout */}
<div className="sm:hidden">
{timeLogs.map(renderMobileTimeLog)}
</div>
{/* Desktop view - Table layout */}
<div className="hidden sm:block overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Date</TableHead>
<TableHead>Time Range</TableHead>
<TableHead>Hours</TableHead>
<TableHead>Comments</TableHead>
<TableHead>Logged By</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{timeLogs.map((log) => (
<TableRow key={log.id}>
<TableCell>
{formatDate(log.workDate)}
</TableCell>
<TableCell>
<Badge variant="outline" className="font-mono bg-indigo-50 text-indigo-700 border-indigo-200 px-2 py-0.5">
{log.startTime} - {log.endTime}
</Badge>
</TableCell>
<TableCell>
<span className="font-medium">{log.hrsSpent.toFixed(2)}</span>
</TableCell>
<TableCell className="max-w-xs">
<div className="truncate" title={stripHtmlTags(log.comments || "No comments")} dangerouslySetInnerHTML={{ __html: log.comments || "No comments" }} />
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Avatar className="h-7 w-7">
<AvatarFallback className="bg-gradient-to-br from-indigo-500 to-purple-500 text-white text-xs">
{getEmployeeNameByUserId(log.createdBy)?.slice(0, 2).toUpperCase() || 'U'}
</AvatarFallback>
</Avatar>
<div className="text-sm font-medium text-gray-800">
{getEmployeeNameByUserId(log.createdBy)}
</div>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</>
)}
</CardContent>
</Card>
);
};
export default TaskTimeLogCard;