diff --git a/frontend/src/components/Calendar/HeatmapDayPanel.jsx b/frontend/src/components/Calendar/HeatmapDayPanel.jsx new file mode 100644 index 0000000..03d2edc --- /dev/null +++ b/frontend/src/components/Calendar/HeatmapDayPanel.jsx @@ -0,0 +1,189 @@ +import { useState } from 'react' +import { format, isToday } from 'date-fns' +import useProjectStore from '../../store/useProjectStore' +import useFocusStore from '../../store/useFocusStore' +import useUIStore from '../../store/useUIStore' +import { updateDeliverable as apiUpdate } from '../../api/deliverables' +import DeliverableModal from '../Deliverables/DeliverableModal' + +const STATUS_KEYS = ['overdue', 'in_progress', 'upcoming', 'completed'] +const STATUS_LABEL = { upcoming: 'Upcoming', in_progress: 'In Progress', completed: 'Completed', overdue: 'Overdue' } +const STATUS_COLOR = { upcoming: 'text-blue-400', in_progress: 'text-amber-400', completed: 'text-green-400', overdue: 'text-red-400' } +const STATUS_BG = { upcoming: 'bg-blue-400/10 border-blue-400/30 hover:bg-blue-400/20', in_progress: 'bg-amber-400/10 border-amber-400/30 hover:bg-amber-400/20', completed: 'bg-green-400/10 border-green-400/30 hover:bg-green-400/20', overdue: 'bg-red-400/10 border-red-400/30 hover:bg-red-400/20' } +const STATUS_CYCLE = { upcoming: 'in_progress', in_progress: 'completed', completed: 'upcoming', overdue: 'in_progress' } + +export default function HeatmapDayPanel({ date, onClose }) { + const projects = useProjectStore(s => s.projects) + const storeUpdate = useProjectStore(s => s.updateDeliverable) + const openFocus = useFocusStore(s => s.openFocus) + const { jumpToCalendarDate } = useUIStore() + + const [addModal, setAddModal] = useState(false) + const [cycling, setCycling] = useState(null) + + const dateStr = format(date, 'yyyy-MM-dd') + + // Derive items live from store so status cycles update instantly + const items = projects.flatMap(p => + (p.deliverables || []) + .filter(d => d.due_date === dateStr) + .map(d => ({ deliverable: d, project: p })) + ) + + const grouped = {} + STATUS_KEYS.forEach(k => { grouped[k] = [] }) + items.forEach(({ deliverable, project }) => { + const s = deliverable.status || 'upcoming' + if (grouped[s]) grouped[s].push({ deliverable, project }) + }) + + const handleCycle = async (deliverable) => { + const next = STATUS_CYCLE[deliverable.status] || 'upcoming' + setCycling(deliverable.id) + try { + const updated = await apiUpdate(deliverable.id, { status: next }) + storeUpdate(updated) + } finally { + setCycling(null) + } + } + + const handleJumpToCalendar = () => { + jumpToCalendarDate(dateStr) + onClose() + } + + return ( + <> + {/* Backdrop */} +
+ + {/* Slide-in panel */} ++ {isToday(date) ? '\u2605 Today' : format(date, 'EEEE')} +
+{format(date, 'MMMM d, yyyy')}
++ {items.length} deliverable{items.length !== 1 ? 's' : ''} +
+Nothing scheduled
+Click + Schedule above to add one
+{deliverable.title}
+{project.name}
++ \u27f3 cycles status \u00b7 \u25ce opens Focus View \u00b7 \u2192 Calendar jumps to date +
+20 weeks of deliverable density by status
{stats[statusKey]}
{STATUS_LABEL[statusKey]}
Click to open day detail