/** * YearlyBlockCalendar - Shows 12-month calendar grid with blocked dates * * Displays: * - Red cells for hard blocks * - Yellow cells for soft blocks * - "B" badge for business-level blocks * - Click to view/edit block * - Year selector */ import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { ChevronLeft, ChevronRight, CalendarDays, X } from 'lucide-react'; import { BlockedDate, TimeBlockListItem } from '../../types'; import { useBlockedDates, useTimeBlock } from '../../hooks/useTimeBlocks'; import { formatLocalDate } from '../../utils/dateUtils'; interface YearlyBlockCalendarProps { resourceId?: string; onBlockClick?: (blockId: string) => void; compact?: boolean; } const MONTHS = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; const WEEKDAYS = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; const YearlyBlockCalendar: React.FC = ({ resourceId, onBlockClick, compact = false, }) => { const { t } = useTranslation(); const [year, setYear] = useState(new Date().getFullYear()); const [selectedBlock, setSelectedBlock] = useState(null); // Fetch blocked dates for the entire year const blockedDatesParams = useMemo(() => ({ start_date: `${year}-01-01`, end_date: `${year + 1}-01-01`, resource_id: resourceId, include_business: true, }), [year, resourceId]); const { data: blockedDates = [], isLoading } = useBlockedDates(blockedDatesParams); // Build a map of date -> blocked dates for quick lookup const blockedDateMap = useMemo(() => { const map = new Map(); blockedDates.forEach(block => { const dateKey = block.date; if (!map.has(dateKey)) { map.set(dateKey, []); } map.get(dateKey)!.push(block); }); return map; }, [blockedDates]); const getDaysInMonth = (month: number): Date[] => { const days: Date[] = []; const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); // Add empty cells for days before the first of the month const startPadding = firstDay.getDay(); for (let i = 0; i < startPadding; i++) { days.push(null as any); } // Add each day of the month for (let day = 1; day <= lastDay.getDate(); day++) { days.push(new Date(year, month, day)); } return days; }; const getBlockStyle = (blocks: BlockedDate[]): string => { // Check if any block is a hard block const hasHardBlock = blocks.some(b => b.block_type === 'HARD'); const hasBusinessBlock = blocks.some(b => b.resource_id === null); if (hasHardBlock) { return hasBusinessBlock ? 'bg-red-500 text-white font-bold' : 'bg-red-400 text-white'; } return hasBusinessBlock ? 'bg-yellow-400 text-yellow-900 font-bold' : 'bg-yellow-300 text-yellow-900'; }; const handleDayClick = (day: Date, blocks: BlockedDate[]) => { if (blocks.length === 0) return; if (blocks.length === 1 && onBlockClick) { onBlockClick(blocks[0].time_block_id); } else { // Show the first block in the popup, could be enhanced to show all setSelectedBlock(blocks[0]); } }; const renderMonth = (month: number) => { const days = getDaysInMonth(month); return (

{MONTHS[month]}

{/* Weekday headers */}
{WEEKDAYS.map((day, i) => (
{day}
))}
{/* Days grid */}
{days.map((day, i) => { if (!day) { return
; } const dateKey = formatLocalDate(day); const blocks = blockedDateMap.get(dateKey) || []; const hasBlocks = blocks.length > 0; const isToday = new Date().toDateString() === day.toDateString(); return ( ); })}
); }; return (
{/* Header with year navigation */}

{t('timeBlocks.yearlyCalendar', 'Yearly Calendar')}

{year}
{/* Legend */}
{t('timeBlocks.hardBlock', 'Hard Block')}
{t('timeBlocks.softBlock', 'Soft Block')}
B
{t('timeBlocks.businessLevel', 'Business Level')}
{/* Loading state */} {isLoading && (
)} {/* Calendar grid */} {!isLoading && (
{Array.from({ length: 12 }, (_, i) => renderMonth(i))}
)} {/* Block detail popup */} {selectedBlock && (
setSelectedBlock(null)}>
e.stopPropagation()}>

{selectedBlock.title}

{t('timeBlocks.type', 'Type')}:{' '} {selectedBlock.block_type === 'HARD' ? t('timeBlocks.hardBlock', 'Hard Block') : t('timeBlocks.softBlock', 'Soft Block')}

{t('common.date', 'Date')}:{' '} {new Date(selectedBlock.date).toLocaleDateString()}

{!selectedBlock.all_day && (

{t('common.time', 'Time')}:{' '} {selectedBlock.start_time} - {selectedBlock.end_time}

)}

{t('timeBlocks.level', 'Level')}:{' '} {selectedBlock.resource_id === null ? t('timeBlocks.businessLevel', 'Business Level') : t('timeBlocks.resourceLevel', 'Resource Level')}

{onBlockClick && ( )}
)}
); }; export default YearlyBlockCalendar;