import React, { useState, useMemo } from 'react'; import { format, startOfMonth, endOfMonth, startOfWeek, endOfWeek, eachDayOfInterval, isSameMonth, isSameDay, addMonths, subMonths, isToday, parseISO } from 'date-fns'; import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Clock } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { TimeLog } from '@/services/timeLogApi'; import { Badge } from '@/components/ui/badge'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; interface Task { id: number; title: string; } interface MonthlyCalendarProps { timeLogs: TimeLog[]; tasks: Task[]; onSelectDate?: (date: Date) => void; isLoading?: boolean; } export const MonthlyCalendar: React.FC = ({ timeLogs, tasks, onSelectDate, isLoading = false }) => { const [currentMonth, setCurrentMonth] = useState(new Date()); const nextMonth = () => setCurrentMonth(addMonths(currentMonth, 1)); const prevMonth = () => setCurrentMonth(subMonths(currentMonth, 1)); const goToToday = () => setCurrentMonth(new Date()); const monthStart = startOfMonth(currentMonth); const monthEnd = endOfMonth(monthStart); const startDate = startOfWeek(monthStart); const endDate = endOfWeek(monthEnd); const calendarDays = eachDayOfInterval({ start: startDate, end: endDate }); const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; // Memoize grouped logs for performance const logsByDate = useMemo(() => { const grouped: Record = {}; timeLogs.forEach(log => { // Create a date string that represents the local date of the workDate // Assuming workDate is ISO string e.g., "2023-10-27T00:00:00.000Z" const dateStr = format(parseISO(log.workDate), 'yyyy-MM-dd'); if (!grouped[dateStr]) { grouped[dateStr] = []; } grouped[dateStr].push(log); }); return grouped; }, [timeLogs]); const getLogsForDate = (date: Date) => { const dateStr = format(date, 'yyyy-MM-dd'); return logsByDate[dateStr] || []; }; const getTaskTitle = (taskId: number) => { const task = tasks.find(t => t.id === taskId); return task ? task.title : `Task #${taskId}`; }; const getTotalHoursForDate = (logs: TimeLog[]) => { return logs.reduce((total, log) => total + log.hrsSpent, 0); }; const formatHours = (hours: number) => Number.isInteger(hours) ? hours : hours.toFixed(1); if (isLoading) { return (

Loading calendar...

); } return (
{/* Calendar Header */}

{format(currentMonth, 'MMMM yyyy')}

{timeLogs.length} entries for selected range

{/* Week Days Header */}
{weekDays.map(day => (
{day}
))}
{/* Calendar Grid */}
{calendarDays.map((day, dayIdx) => { const logs = getLogsForDate(day); const totalHours = getTotalHoursForDate(logs); const isCurrentMonth = isSameMonth(day, currentMonth); const isDayToday = isToday(day); return (
onSelectDate && onSelectDate(day)} >
{format(day, 'd')} {totalHours > 0 && (
= 8 ? "bg-emerald-100 text-emerald-700" : totalHours > 4 ? "bg-indigo-100 text-indigo-700" : "bg-amber-100 text-amber-700" )}> {formatHours(totalHours)}h
)}
{logs.map((log, idx) => (
{getTaskTitle(log.taskId)}
{log.startTime} {formatHours(log.hrsSpent)}h
Task Details {log.hrsSpent}h

{getTaskTitle(log.taskId)}

{log.comments && (
)}
{log.startTime} - {log.endTime}
))}
); })}
); };