diff --git a/frontend/src/App.js b/frontend/src/App.js index 5c370ca..002acef 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -9,6 +9,7 @@ import NvdSyncModal from './components/NvdSyncModal'; import KnowledgeBaseModal from './components/KnowledgeBaseModal'; import KnowledgeBaseViewer from './components/KnowledgeBaseViewer'; import NavDrawer from './components/NavDrawer'; +import CalendarWidget from './components/CalendarWidget'; import ReportingPage from './components/pages/ReportingPage'; import KnowledgeBasePage from './components/pages/KnowledgeBasePage'; import ExportsPage from './components/pages/ExportsPage'; @@ -2219,63 +2220,7 @@ export default function App() { Calendar - {/* Simple Calendar Grid */} -
-
- February 2024 -
-
-
Su
-
Mo
-
Tu
-
We
-
Th
-
Fr
-
Sa
-
-
- {/* Week 1 */} -
28
-
29
-
30
-
31
-
1
-
2
-
3
- {/* Week 2 */} -
4
-
5
-
6
-
7
-
8
-
9
-
10
- {/* Week 3 */} -
11
-
12
-
13
-
14
-
15
-
16
-
17
- {/* Week 4 */} -
18
-
19
-
20
-
21
-
22
-
23
-
24
- {/* Week 5 */} -
25
-
26
-
27
-
28
-
29
-
1
-
2
-
-
+ {/* Open Vendor Tickets */} diff --git a/frontend/src/components/CalendarWidget.js b/frontend/src/components/CalendarWidget.js new file mode 100644 index 0000000..1a9771a --- /dev/null +++ b/frontend/src/components/CalendarWidget.js @@ -0,0 +1,163 @@ +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 + +
+ )} +
+ ); +}