import React, { useState, useMemo } from 'react'; import { format, startOfMonth, endOfMonth, eachDayOfInterval, isSameDay, parseISO, isWithinInterval, startOfDay, endOfDay } from 'date-fns'; import { Task } from '@/services/tasksApi'; import { Employee } from '@/types'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { ChevronLeft, ChevronRight, X, Calendar as CalendarIcon, User, ListFilter, Clock } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { useNavigate } from "react-router-dom"; import { cn } from "@/lib/utils"; import { CustomSheet } from "@/components/ui/custom-sheet"; interface TaskMatrixViewProps { tasks: Task[]; employees: Employee[]; } interface SelectionState { type: 'cell' | 'employee' | 'day'; id: string; // Unique identifier for highlighting title: string; subTitle?: string; tasks: Task[]; } export const TaskMatrixView: React.FC = ({ tasks, employees }) => { const navigate = useNavigate(); const [currentDate, setCurrentDate] = useState(new Date()); const [selection, setSelection] = useState(null); const [selectedTask, setSelectedTask] = useState(null); const [isSheetOpen, setIsSheetOpen] = useState(false); // Get all days in the current month const daysInMonth = useMemo(() => { const start = startOfMonth(currentDate); const end = endOfMonth(currentDate); return eachDayOfInterval({ start, end }); }, [currentDate]); // Navigate months const nextMonth = () => { setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1)); setSelection(null); }; const prevMonth = () => { setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1)); setSelection(null); }; // Calculate matrix data (counts and tasks per cell) const matrixData = useMemo(() => { const data: Record> = {}; const dailyTotals: Record = {}; const dailyTasks: Record = {}; // Store all tasks for a day const employeeTasks: Record = {}; // Store all monthly tasks for an employee // Initialize daily totals daysInMonth.forEach(day => { const dayStr = format(day, 'yyyy-MM-dd'); dailyTotals[dayStr] = 0; dailyTasks[dayStr] = []; }); // Initialize employee rows employees.forEach(emp => { data[emp.id] = {}; employeeTasks[emp.id] = []; daysInMonth.forEach(day => { const dayStr = format(day, 'yyyy-MM-dd'); data[emp.id][dayStr] = []; }); }); // Loop through each task tasks.forEach(task => { if (!task.assignedTo) return; // Skip tasks for employees not in the current list if (!data[task.assignedTo]) return; const startDate = task.stateDate ? parseISO(task.stateDate) : (task.createdAt ? parseISO(task.createdAt) : new Date()); let endDate = task.endDate ? parseISO(task.endDate) : startDate; if (endDate < startDate) endDate = startDate; const monthStart = startOfMonth(currentDate); const monthEnd = endOfMonth(currentDate); if (endDate < monthStart || startDate > monthEnd) return; // Track if we've added this task to the employee's monthly list already let addedToEmployee = false; daysInMonth.forEach(day => { const dayStr = format(day, 'yyyy-MM-dd'); const currentDayStart = startOfDay(day); const rangeStart = startOfDay(startDate); const rangeEnd = endOfDay(endDate); if (currentDayStart >= rangeStart && currentDayStart <= rangeEnd) { if (data[task.assignedTo] && data[task.assignedTo][dayStr] !== undefined) { data[task.assignedTo][dayStr].push(task); dailyTotals[dayStr] = (dailyTotals[dayStr] || 0) + 1; // Add to day aggregate (ensure uniqueness if needed, but tasks are unique per iteration) dailyTasks[dayStr].push(task); // Add to employee aggregate if (!addedToEmployee) { employeeTasks[task.assignedTo].push(task); addedToEmployee = true; } } } }); }); return { employeeData: data, dailyTotals, dailyTasks, employeeTasks }; }, [tasks, employees, daysInMonth, currentDate]); // Handle selection interactions const handleCellClick = (employee: Employee, day: Date, tasks: Task[]) => { if (tasks.length > 0) { setSelection({ type: 'cell', id: `${employee.id}-${format(day, 'yyyy-MM-dd')}`, title: `Tasks for ${employee.firstName} ${employee.lastName}`, subTitle: format(day, "EEEE, MMMM do, yyyy"), tasks: tasks }); } }; const handleEmployeeHeaderClick = (employee: Employee) => { const tasks = matrixData.employeeTasks[employee.id] || []; if (tasks.length > 0) { setSelection({ type: 'employee', id: employee.id.toString(), title: `Tasks for ${employee.firstName} ${employee.lastName}`, subTitle: `Total for ${format(currentDate, 'MMMM yyyy')}`, tasks: tasks }); } }; const handleDayHeaderClick = (day: Date) => { const dayStr = format(day, 'yyyy-MM-dd'); const tasks = matrixData.dailyTasks[dayStr] || []; if (tasks.length > 0) { setSelection({ type: 'day', id: dayStr, title: `All Tasks for ${format(day, "MMMM do")}`, subTitle: format(day, "EEEE, yyyy"), tasks: tasks }); } }; const handleEditTask = (task: Task) => { setSelectedTask(task); setIsSheetOpen(true); }; return (
Monthly employee and day wise task report
{format(currentDate, 'MMMM yyyy')}
{/* Key change: Added max-height and scrolling to the container */}
Employee Name {daysInMonth.map((day) => { const dayStr = format(day, 'yyyy-MM-dd'); const isSelected = selection?.type === 'day' && selection.id === dayStr; return ( handleDayHeaderClick(day)} >
{format(day, 'EEE')} {format(day, 'd')}
); })}
{employees.map((employee) => { const isRowSelected = selection?.type === 'employee' && selection.id === employee.id.toString(); return ( handleEmployeeHeaderClick(employee)} >
{employee.firstName} {employee.lastName}
{daysInMonth.map((day) => { const dayStr = format(day, 'yyyy-MM-dd'); const cellTasks = matrixData.employeeData[employee.id]?.[dayStr] || []; const count = cellTasks.length; const cellId = `${employee.id}-${dayStr}`; // Determine highlighting based on selection type const isCellSelected = selection?.type === 'cell' && selection.id === cellId; const isColSelected = selection?.type === 'day' && selection.id === dayStr; // Base background color logic let bgClass = ""; if (isCellSelected) bgClass = "bg-primary/20 ring-1 ring-primary ring-inset z-10"; else if (isRowSelected || isColSelected) bgClass = "bg-primary/5"; return ( 0 ? "cursor-pointer hover:bg-primary/10" : "", bgClass )} onClick={() => count > 0 && handleCellClick(employee, day, cellTasks)} >
{count > 0 ? ( {count} ) : ( - )}
); })}
); })} {/* Totals Row */} Total Task {daysInMonth.map((day) => { const dayStr = format(day, 'yyyy-MM-dd'); return ( {matrixData.dailyTotals[dayStr] || 0} ); })}
{/* Selected Task List */} {selection && (
{selection.type === 'employee' && } {selection.type === 'day' && } {selection.type === 'cell' && } {selection.title} {selection.subTitle && (

{selection.subTitle}

)}
Title Status Start Date End Date {selection.tasks.map((task) => ( handleEditTask(task)} > {task.title} {task.status} {task.stateDate ? format(parseISO(task.stateDate), 'MMM d, yyyy') : '-'} {task.endDate ? format(parseISO(task.endDate), 'MMM d, yyyy') : '-'} ))}
)} {/* Task Details Sheet - REPLACED WITH CUSTOM SHEET */} {selectedTask && (
{selectedTask.status} {selectedTask.type} Priority: {selectedTask.priority}

Start Date

{selectedTask.stateDate ? format(parseISO(selectedTask.stateDate), 'PPP') : 'Not set'}

End Date

{selectedTask.endDate ? format(parseISO(selectedTask.endDate), 'PPP') : 'Not set'}

Assignee

{employees.find(e => e.id === selectedTask.assignedTo)?.firstName} {employees.find(e => e.id === selectedTask.assignedTo)?.lastName || 'Unassigned'}

Created At

{selectedTask.createdAt ? format(parseISO(selectedTask.createdAt), 'PPP') : 'Unknown'}

Description

{selectedTask.description || 'No description provided.'}
)}
); };