diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js index 6a8c6fb..5e924d1 100644 --- a/frontend/js/calendar.js +++ b/frontend/js/calendar.js @@ -141,7 +141,7 @@ function prefetchIfNeeded(viewStart, viewEnd) { } // ── Data fetching ───────────────────────────────────────── -async function fetchAndRender(force = false) { +async function fetchAndRender(force = false, silent = false) { const { start, end } = getViewRange(); // Cache hit: requested range is fully within what we already have @@ -159,7 +159,7 @@ async function fetchAndRender(force = false) { const fetchStart = new Date(start.getTime() - CACHE_BUF); const fetchEnd = new Date(end.getTime() + CACHE_BUF); - showLoading(); + if (!silent) showLoading(); try { const resp = await api.get(`/caldav/events?start=${fetchStart.toISOString()}&end=${fetchEnd.toISOString()}`); const events = resp.events || resp; @@ -460,6 +460,8 @@ function renderCalendarList() { container.querySelectorAll('input[type=checkbox]').forEach(cb => { cb.addEventListener('change', async () => { const source = cb.dataset.source; + let cacheCalId = null; // calendar_id value used in cached events + if (source === 'caldav') { const calId = parseInt(cb.dataset.calId); await api.put(`/caldav/calendars/${calId}`, { enabled: cb.checked }); @@ -468,16 +470,19 @@ function renderCalendarList() { if (cal.id === calId) cal.enabled = cb.checked; } } + cacheCalId = calId; // numeric integer in cached events } else if (source === 'local') { const calId = parseInt(cb.dataset.calId); await api.put(`/local/calendars/${calId}`, { enabled: cb.checked }); const cal = state.localCalendars.find(c => c.id === calId); if (cal) cal.enabled = cb.checked; + cacheCalId = `local-${calId}`; } else if (source === 'ical') { const subId = parseInt(cb.dataset.subId); await api.put(`/ical/subscriptions/${subId}`, { enabled: cb.checked }); const sub = state.icalSubscriptions.find(s => s.id === subId); if (sub) sub.enabled = cb.checked; + cacheCalId = `ical-${subId}`; } else if (source === 'google') { const calId = parseInt(cb.dataset.calId); await api.put(`/google/calendars/${calId}`, { enabled: cb.checked }); @@ -485,8 +490,20 @@ function renderCalendarList() { const cal = acc.calendars.find(c => c.id === calId); if (cal) cal.enabled = cb.checked; } + cacheCalId = `google-${calId}`; + } + + if (!cb.checked && cacheCalId !== null) { + // Hiding: filter from cache instantly, no network call needed + eventCache.events = eventCache.events.filter(ev => ev.calendar_id !== cacheCalId); + state.events = eventCache.events; + renderView(); + updateTitle(); + renderMiniCal(); + } else { + // Showing: refetch silently — view stays visible, updates when done + fetchAndRender(true, true); } - fetchAndRender(true); }); }); diff --git a/frontend/js/views/week.js b/frontend/js/views/week.js index 087ed6d..d16efe4 100644 --- a/frontend/js/views/week.js +++ b/frontend/js/views/week.js @@ -131,10 +131,24 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC }).join(''); // Background tint for days covered by multi-day events (timed or all-day) - const tintHtml = tintEvs.filter(ev => spansDay(ev, day)).map(ev => { - const color = ev.color || ev.calendarColor || '#4285f4'; - return `
`; - }).join(''); + const dayTintEvs = tintEvs.filter(ev => spansDay(ev, day)); + const tintHtml = (() => { + if (!dayTintEvs.length) return ''; + const colors = dayTintEvs.map(ev => ev.color || ev.calendarColor || '#4285f4'); + let bg; + if (colors.length === 1) { + bg = colors[0] + '26'; + } else { + // Vertical gradient bands for multiple overlapping multi-day events + const stops = colors.flatMap((c, i) => { + const p1 = ((i / colors.length) * 100).toFixed(1); + const p2 = (((i + 1) / colors.length) * 100).toFixed(1); + return [`${c}26 ${p1}%`, `${c}26 ${p2}%`]; + }).join(','); + bg = `linear-gradient(to bottom,${stops})`; + } + return ``; + })(); return `