import { useRef, useState, useCallback, useEffect } from 'react' import FullCalendar from '@fullcalendar/react' import dayGridPlugin from '@fullcalendar/daygrid' import timeGridPlugin from '@fullcalendar/timegrid' import interactionPlugin from '@fullcalendar/interaction' import useProjectStore from '../../store/useProjectStore' import useFocusStore from '../../store/useFocusStore' import useUIStore from '../../store/useUIStore' import useToastStore from '../../store/useToastStore' import { updateDeliverable, deleteDeliverable } from '../../api/deliverables' import DeliverableModal from '../Deliverables/DeliverableModal' import ContextMenu from '../UI/ContextMenu' import EventTooltip from './EventTooltip' import WorkloadHeatmap from './WorkloadHeatmap' export default function MainCalendar({ onCalendarReady }) { const calRef = useRef(null) const wrapperRef = useRef(null) const { projects, updateDeliverable: storeUpdate, removeDeliverable } = useProjectStore() const openFocus = useFocusStore(s => s.openFocus) const { showHeatmap, toggleHeatmap, heatmapJumpDate, clearJumpDate } = useUIStore() const addToast = useToastStore(s => s.addToast) const [modal, setModal] = useState({ open: false, deliverable: null, defaultDate: '' }) const [contextMenu, setContextMenu] = useState(null) const [tooltip, setTooltip] = useState(null) // Expose calendar API to App.jsx for keyboard shortcuts useEffect(() => { if (calRef.current && onCalendarReady) onCalendarReady(calRef.current.getApi()) }, []) // ResizeObserver: smooth reflow during sidebar CSS transition useEffect(() => { const el = wrapperRef.current if (!el) return const ro = new ResizeObserver(() => calRef.current?.getApi().updateSize()) ro.observe(el) return () => ro.disconnect() }, []) // Jump to date commanded by HeatmapDayPanel useEffect(() => { if (!heatmapJumpDate || !calRef.current) return calRef.current.getApi().gotoDate(heatmapJumpDate) clearJumpDate() }, [heatmapJumpDate, clearJumpDate]) const events = projects.flatMap(p => (p.deliverables || []).map(d => ({ id: String(d.id), title: `${p.name}: ${d.title}`, start: d.due_date, allDay: true, backgroundColor: p.color, borderColor: p.color, extendedProps: { deliverableId: d.id, projectId: p.id }, })) ) const getCtx = (projectId, deliverableId) => { const project = projects.find(p => p.id === projectId) const deliverable = project?.deliverables.find(d => d.id === deliverableId) return { project, deliverable } } const handleEventClick = useCallback(({ event }) => { const { deliverableId, projectId } = event.extendedProps openFocus(projectId, deliverableId) }, [openFocus]) const handleEventDrop = useCallback(async ({ event, oldEvent }) => { const { deliverableId } = event.extendedProps const newDate = event.startStr.substring(0, 10) const oldDate = oldEvent.startStr.substring(0, 10) storeUpdate(await updateDeliverable(deliverableId, { due_date: newDate })) addToast({ message: `Moved to ${newDate}`, duration: 30, undoFn: async () => { storeUpdate(await updateDeliverable(deliverableId, { due_date: oldDate })) }, }) }, [storeUpdate, addToast]) const handleDateClick = useCallback(({ dateStr }) => { setModal({ open: true, deliverable: null, defaultDate: dateStr.substring(0, 10) }) }, []) const handleSelect = useCallback(({ startStr }) => { setModal({ open: true, deliverable: null, defaultDate: startStr.substring(0, 10) }) }, []) const handleEventDidMount = useCallback(({ event, el }) => { const { deliverableId, projectId } = event.extendedProps el.addEventListener('mouseenter', (e) => { const { project, deliverable } = getCtx(projectId, deliverableId) setTooltip({ x: e.clientX, y: e.clientY, project, deliverable }) }) el.addEventListener('mouseleave', () => setTooltip(null)) el.addEventListener('mousemove', (e) => { setTooltip(prev => prev ? { ...prev, x: e.clientX, y: e.clientY } : null) }) el.addEventListener('dblclick', (e) => { e.preventDefault(); e.stopPropagation() setTooltip(null) const { deliverable } = getCtx(projectId, deliverableId) if (deliverable) setModal({ open: true, deliverable, defaultDate: '' }) }) el.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation() setTooltip(null) const { project, deliverable } = getCtx(projectId, deliverableId) if (!deliverable) return setContextMenu({ x: e.clientX, y: e.clientY, items: [ { icon: '✔︎', label: 'Edit Deliverable', action: () => setModal({ open: true, deliverable, defaultDate: '' }) }, { icon: '❖', label: 'Open Focus View', action: () => openFocus(projectId, deliverableId) }, ...(project?.drive_url ? [{ icon: '⬡', label: 'Open Drive Folder', action: () => window.open(project.drive_url, '_blank') }] : []), { separator: true }, { icon: '✕', label: 'Delete Deliverable', danger: true, action: async () => { if (window.confirm(`Delete "${deliverable.title}"?`)) { await deleteDeliverable(deliverableId) removeDeliverable(deliverableId) } }, }, ], }) }) }, [projects, openFocus]) return (