import React, { useState, useEffect } from 'react'; import { ChevronLeft, ChevronRight } from 'lucide-react'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; const MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; const DAY_NAMES = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; function toLocalDateStr(date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); return `${y}-${m}-${d}`; } export default function CalendarWidget() { const today = new Date(); const todayStr = toLocalDateStr(today); const [calYear, setCalYear] = useState(today.getFullYear()); const [calMonth, setCalMonth] = useState(today.getMonth()); // 0-indexed // Map of "YYYY-MM-DD" → count of findings due that day const [dueDates, setDueDates] = useState({}); useEffect(() => { fetch(`${API_BASE}/ivanti/findings`, { credentials: 'include' }) .then((r) => (r.ok ? r.json() : null)) .then((data) => { if (!data?.findings) return; const counts = {}; data.findings.forEach((f) => { if (f.dueDate) { counts[f.dueDate] = (counts[f.dueDate] || 0) + 1; } }); setDueDates(counts); }) .catch(() => {}); }, []); const prevMonth = () => { if (calMonth === 0) { setCalMonth(11); setCalYear((y) => y - 1); } else { setCalMonth((m) => m - 1); } }; const nextMonth = () => { if (calMonth === 11) { setCalMonth(0); setCalYear((y) => y + 1); } else { setCalMonth((m) => m + 1); } }; // Build cell array: null = padding, number = day of month const firstDow = new Date(calYear, calMonth, 1).getDay(); // 0=Sun const daysInMonth = new Date(calYear, calMonth + 1, 0).getDate(); const cells = [ ...Array(firstDow).fill(null), ...Array.from({ length: daysInMonth }, (_, i) => i + 1), ]; while (cells.length % 7 !== 0) cells.push(null); // complete last row const hasDueDatesThisMonth = cells.some((day) => { if (!day) return false; const ds = `${calYear}-${String(calMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; return !!dueDates[ds]; }); return (
{/* Month navigation */}
{MONTH_NAMES[calMonth]} {calYear}
{/* Day-of-week headers */}
{DAY_NAMES.map((d) => (
{d}
))}
{/* Day cells */}
{cells.map((day, idx) => { if (!day) return
; const dateStr = `${calYear}-${String(calMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const isToday = dateStr === todayStr; const dueCount = dueDates[dateStr] || 0; const hasDue = dueCount > 0; return (
1 ? 's' : ''} due` : undefined} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px', padding: '3px 1px', borderRadius: '4px', background: isToday ? 'rgba(14,165,233,0.2)' : 'transparent', border: isToday ? '1px solid rgba(14,165,233,0.5)' : '1px solid transparent', cursor: hasDue ? 'default' : 'default', }} > {day} {/* Red dot indicator for due dates */} {hasDue ? (
) : (
// spacer to keep rows even )}
); })}
{/* Legend — only shown when there are due dates this month */} {hasDueDatesThisMonth && (
Ivanti finding due
)}
); }