import { TimeSlot, Participant } from '@/types/calendar'; import { cn } from '@/lib/utils'; import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; import { Button } from '@/components/ui/button'; import { Check, X, Loader2 } from 'lucide-react'; const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; const hours = [9, 10, 11, 12, 13, 14, 15, 16, 17]; // Get the dates for Mon-Fri of the current week const getWeekDates = () => { const now = new Date(); const monday = new Date(now); monday.setDate(now.getDate() - now.getDay() + 1); monday.setHours(0, 0, 0, 0); return dayNames.map((_, i) => { const date = new Date(monday); date.setDate(monday.getDate() + i); return date.toISOString().split('T')[0]; // "YYYY-MM-DD" }); }; interface AvailabilityHeatmapProps { slots: TimeSlot[]; selectedParticipants: Participant[]; onSlotSelect: (slot: TimeSlot) => void; showPartialAvailability?: boolean; isLoading?: boolean; } export const AvailabilityHeatmap = ({ slots, selectedParticipants, onSlotSelect, showPartialAvailability = false, isLoading = false, }: AvailabilityHeatmapProps) => { const weekDates = getWeekDates(); const getSlot = (dateStr: string, hour: number) => { return slots.find((s) => s.day === dateStr && s.hour === hour); }; const getEffectiveAvailability = (slot: TimeSlot) => { if (slot.availability === 'partial' && !showPartialAvailability) { return 'none'; } return slot.availability; }; const formatHour = (hour: number) => { return `${hour.toString().padStart(2, '0')}:00`; }; const isSlotTooSoon = (dateStr: string, hour: number) => { const slotTime = new Date(`${dateStr}T${formatHour(hour)}:00Z`); const now = new Date(); const twoHoursFromNow = new Date(now.getTime() + 2 * 60 * 60 * 1000); return slotTime < twoHoursFromNow; }; const getWeekDateRange = () => { const now = new Date(); const monday = new Date(now); monday.setDate(now.getDate() - now.getDay() + 1); const friday = new Date(monday); friday.setDate(monday.getDate() + 4); const format = (d: Date) => d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); return `${format(monday)} – ${format(friday)}`; }; if (selectedParticipants.length === 0) { return (

Select participants to view availability

Add people from the search above to see their common free times

); } if (isLoading) { return (

Loading availability...

); } return (

Common Availability — Week of {getWeekDateRange()}

{selectedParticipants.length} participant{selectedParticipants.length > 1 ? 's' : ''}: {selectedParticipants.map(p => p.name.split(' ')[0]).join(', ')}

{dayNames.map((dayName) => (
{dayName}
))}
{hours.map((hour) => (
{formatHour(hour)}
{weekDates.map((dateStr, dayIndex) => { const slot = getSlot(dateStr, hour); const dayName = dayNames[dayIndex]; const tooSoon = isSlotTooSoon(dateStr, hour); if (!slot) return
; const effectiveAvailability = getEffectiveAvailability(slot); return ( )}
); })}
))}
All free
{showPartialAvailability && (
Partial
)}
No overlap
); };