diff --git a/frontend/src/components/Calendar.jsx b/frontend/src/components/Calendar.jsx
index 49fb50b..c0d2a79 100644
--- a/frontend/src/components/Calendar.jsx
+++ b/frontend/src/components/Calendar.jsx
@@ -20,7 +20,7 @@ const COLOR_PRESETS = [
{ name: 'Pink', value: '#ec4899' }
];
-export const getDDayText = (targetDate) => {
+const getDDayText = (targetDate) => {
if (!targetDate) return '';
const today = new Date();
today.setHours(0, 0, 0, 0);
@@ -32,7 +32,7 @@ export const getDDayText = (targetDate) => {
return `D+${Math.abs(diff)}`;
};
-export const formatDateStr = (d) => {
+const formatDateStr = (d) => {
const yr = d.getFullYear();
const mo = String(d.getMonth() + 1).padStart(2, '0');
const dy = String(d.getDate()).padStart(2, '0');
@@ -97,7 +97,7 @@ const getRruleLabel = (rule) => {
// Splits a stored timestamp into an editable date ('YYYY-MM-DD') + time ('HH:MM').
// Handles naive values ('2026-09-01', '2026-09-01T17:15') and full Google ISO
// values ('2026-09-01T17:15:00+05:30') — keeps the wall-clock time as-is.
-export const splitDateTime = (value) => {
+const splitDateTime = (value) => {
if (!value) return { date: '', time: '' };
const s = String(value);
const m = s.match(/^(\d{4}-\d{2}-\d{2})(?:T(\d{2}):(\d{2})(?::\d{2})?.*)?$/);
@@ -106,7 +106,7 @@ export const splitDateTime = (value) => {
};
// "5:15 PM" style wall-clock display for the detail panel.
-export const getEventTime = (ev) => {
+const getEventTime = (ev) => {
if (!ev || ev.is_all_day || !ev.event_date) return '';
const { time } = splitDateTime(ev.event_date);
if (!time) return '';
@@ -313,7 +313,7 @@ const CalendarCell = memo(({ dateStr, day, currentMonth, isToday, isSelected, da
// Does this event occupy the given calendar day (YYYY-MM-DD)?
// Handles single/multi-day spans AND recurring events (DAILY/WEEKLY/MONTHLY/YEARLY
// with optional INTERVAL). For recurring events the optional end_date caps the series.
-export const isEventOnDate = (ev, dateStr) => {
+const isEventOnDate = (ev, dateStr) => {
const startStr = ev.event_date ? String(ev.event_date).slice(0, 10) : '';
if (!startStr) return false;
// A single occurrence was "deleted" via the exclude endpoint — skip that date.
@@ -761,12 +761,8 @@ const Calendar = () => {
const res = await apiFetch('/api/calendar/events');
if (res && res.ok) {
const data = await res.json();
- if (Array.isArray(data)) {
- setEvents(data);
- window.dispatchEvent(new CustomEvent('snowball-calendar-updated', { detail: data }));
- } else {
- setFetchError(true);
- }
+ if (Array.isArray(data)) setEvents(data);
+ else setFetchError(true);
} else {
setFetchError(true);
}
diff --git a/frontend/src/components/CalendarSummaryCard.jsx b/frontend/src/components/CalendarSummaryCard.jsx
deleted file mode 100644
index 2f73354..0000000
--- a/frontend/src/components/CalendarSummaryCard.jsx
+++ /dev/null
@@ -1,446 +0,0 @@
-import React, { useState, useEffect, useCallback, useMemo } from 'react';
-import {
- CalendarDays, ChevronDown, ChevronUp, Clock,
- Repeat, Plus, ExternalLink, Timer, Sparkles
-} from 'lucide-react';
-import { apiFetch } from '../utils/apiClient.js';
-import { getTagColor, loadTagColors, parseTags, getReadableTextColor } from '../utils/tagColors.js';
-import {
- getDDayText, formatDateStr, splitDateTime,
- getEventTime, isEventOnDate
-} from './Calendar.jsx';
-
-const CalendarSummaryCard = ({ onOpenCalendar }) => {
- const [isExpanded, setIsExpanded] = useState(() => {
- const saved = localStorage.getItem('snowball_calendar_summary_expanded');
- return saved !== null ? JSON.parse(saved) : true;
- });
-
- const [events, setEvents] = useState([]);
- const [loading, setLoading] = useState(true);
- const [tagColors, setTagColors] = useState(() => loadTagColors());
-
- useEffect(() => {
- localStorage.setItem('snowball_calendar_summary_expanded', JSON.stringify(isExpanded));
- }, [isExpanded]);
-
- const fetchEvents = useCallback(async () => {
- try {
- const res = await apiFetch('/api/calendar/events');
- if (res && res.ok) {
- const data = await res.json();
- if (Array.isArray(data)) setEvents(data);
- }
- } catch (err) {
- console.error('Failed to fetch calendar events in summary card:', err);
- } finally {
- setLoading(false);
- }
- }, []);
-
- useEffect(() => {
- fetchEvents();
-
- const handleCalendarUpdated = (e) => {
- if (e?.detail && Array.isArray(e.detail)) {
- setEvents(e.detail);
- } else {
- fetchEvents();
- }
- };
-
- const handleTagColorsChanged = () => {
- setTagColors(loadTagColors());
- };
-
- window.addEventListener('snowball-calendar-updated', handleCalendarUpdated);
- window.addEventListener('snowball-tag-colors-changed', handleTagColorsChanged);
-
- return () => {
- window.removeEventListener('snowball-calendar-updated', handleCalendarUpdated);
- window.removeEventListener('snowball-tag-colors-changed', handleTagColorsChanged);
- };
- }, [fetchEvents]);
-
- const today = useMemo(() => new Date(), []);
- const todayStr = useMemo(() => formatDateStr(today), [today]);
-
- // Compute today's scheduled events (handling recurring + multi-day spans)
- const todayEvents = useMemo(() => {
- const list = events.filter((ev) => isEventOnDate(ev, todayStr));
- return list.sort((a, b) => {
- if (a.is_all_day && !b.is_all_day) return -1;
- if (!a.is_all_day && b.is_all_day) return 1;
- const timeA = splitDateTime(a.event_date).time || '00:00';
- const timeB = splitDateTime(b.event_date).time || '00:00';
- return timeA.localeCompare(timeB);
- });
- }, [events, todayStr]);
-
- // Compute upcoming D-Day countdowns
- const ddayList = useMemo(() => {
- const todayStart = new Date(today);
- todayStart.setHours(0, 0, 0, 0);
-
- return events
- .filter((e) => e.is_dday && e.dday_target_date)
- .map((e) => {
- const target = new Date(e.dday_target_date);
- target.setHours(0, 0, 0, 0);
- const diff = Math.ceil((target - todayStart) / (1000 * 60 * 60 * 24));
- return {
- ...e,
- diff,
- dText: getDDayText(e.dday_target_date)
- };
- })
- .sort((a, b) => {
- // Future/today first (diff >= 0), ascending by distance
- if (a.diff >= 0 && b.diff < 0) return -1;
- if (a.diff < 0 && b.diff >= 0) return 1;
- if (a.diff >= 0 && b.diff >= 0) return a.diff - b.diff;
- // Past D-Days descending
- return b.diff - a.diff;
- });
- }, [events, today]);
-
- const nearestDDay = ddayList.length > 0 ? ddayList[0] : null;
-
- const formattedDate = useMemo(() => {
- return today.toLocaleDateString(undefined, {
- weekday: 'short',
- month: 'short',
- day: 'numeric'
- });
- }, [today]);
-
- return (
-
- {/* Header with Title, D-Day counter pill, and Collapse trigger */}
-
setIsExpanded(!isExpanded)}
- style={{
- padding: '0.75rem 1rem',
- background: 'var(--bg-secondary)',
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- cursor: 'pointer',
- borderBottom: isExpanded ? '1px solid var(--border-color)' : 'none',
- gap: '0.5rem'
- }}
- >
-
-
-
- Schedule
-
-
-
-
- {nearestDDay && (
-
{
- e.stopPropagation();
- if (onOpenCalendar) onOpenCalendar();
- }}
- title={`D-Day Target: ${nearestDDay.title} (${nearestDDay.dday_target_date})`}
- style={{
- display: 'flex',
- alignItems: 'center',
- gap: '0.35rem',
- padding: '2px 8px',
- borderRadius: '999px',
- fontSize: '0.72rem',
- fontWeight: '700',
- cursor: 'pointer',
- background:
- nearestDDay.diff === 0
- ? 'rgba(239, 68, 68, 0.15)'
- : 'rgba(var(--accent-rgb), 0.12)',
- border:
- nearestDDay.diff === 0
- ? '1px solid rgba(239, 68, 68, 0.35)'
- : '1px solid rgba(var(--accent-rgb), 0.25)',
- color: nearestDDay.diff === 0 ? '#ef4444' : 'var(--accent-color)',
- transition: 'all 0.15s ease'
- }}
- >
-
- {nearestDDay.dText}
- |
-
- {nearestDDay.title}
-
-
- )}
-
-
- {isExpanded ? : }
-
-
-
-
- {/* Expanded Body */}
- {isExpanded && (
-
- {/* Sub-bar: Overview date and Google sync status */}
-
-
-
- {todayEvents.length} event{todayEvents.length === 1 ? '' : 's'} today
-
- •
- {formattedDate}
-
-
-
- {/* Today's Events Timeline */}
-
- {loading ? (
-
- Loading schedule...
-
- ) : todayEvents.length === 0 ? (
-
-
-
- No events scheduled for today
-
-
- ) : (
- todayEvents.map((ev) => {
- const parsedTags = parseTags(ev.tags);
- const eventTime = getEventTime(ev);
- const isRecurring = /FREQ=/.test(String(ev.recurrence_rule || ''));
-
- return (
-
{
- if (onOpenCalendar) onOpenCalendar();
- }}
- style={{
- display: 'flex',
- alignItems: 'flex-start',
- gap: '0.65rem',
- padding: '0.5rem 0.65rem',
- borderRadius: 'calc(var(--radius) - 2px)',
- background: 'var(--bg-primary)',
- border: '1px solid var(--border-color)',
- cursor: 'pointer',
- transition: 'border-color 0.15s ease'
- }}
- onMouseEnter={(e) => {
- e.currentTarget.style.borderColor = 'var(--accent-color)';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.borderColor = 'var(--border-color)';
- }}
- >
- {/* Time badge */}
-
- {ev.is_all_day ? 'All Day' : eventTime || 'Scheduled'}
-
-
- {/* Event Details */}
-
-
-
- {ev.title}
-
-
- {isRecurring && (
-
- )}
-
-
- {/* Tag badges */}
- {parsedTags.length > 0 && (
-
- {parsedTags.map((tag) => {
- const bg = getTagColor(tag, tagColors);
- const fg = getReadableTextColor(bg);
- return (
-
- {tag}
-
- );
- })}
-
- )}
-
-
- );
- })
- )}
-
-
- {/* Footer Quick Action Buttons */}
-
-
-
-
-
-
- )}
-
- );
-};
-
-export default CalendarSummaryCard;