File size: 8,192 Bytes
d97b8f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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;