diff --git a/package.json b/package.json index 54a8987..75f5cc1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.64.0", + "version": "1.65.0", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 0410e1b..35975f8 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -3592,6 +3592,79 @@ export default function WeeklyView() { [calendarEvents, effectiveCellDuration], ); + // Precompute overlap layout: { [eventId]: { column, totalColumns } } + const eventOverlapLayout = useMemo(() => { + const layout: Record = {}; + // Group events by day + const dayMap = new Map(); + for (const event of calendarEvents) { + if (isAllDayEvent(event)) continue; + const d = new Date(event.startTime); + const key = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`; + if (!dayMap.has(key)) dayMap.set(key, []); + dayMap.get(key)!.push(event); + } + for (const events of dayMap.values()) { + // Sort by start time, then by duration (longer first) + events.sort((a, b) => { + const diff = new Date(a.startTime).getTime() - new Date(b.startTime).getTime(); + if (diff !== 0) return diff; + return (new Date(b.endTime).getTime() - new Date(b.startTime).getTime()) - + (new Date(a.endTime).getTime() - new Date(a.startTime).getTime()); + }); + // Build overlap groups using a greedy column assignment + const columns: { end: number; eventId: string }[][] = []; + for (const event of events) { + const start = new Date(event.startTime).getTime(); + const end = new Date(event.endTime).getTime(); + // Find first column where this event doesn't overlap + let placed = false; + for (let col = 0; col < columns.length; col++) { + const lastInCol = columns[col][columns[col].length - 1]; + if (lastInCol.end <= start) { + columns[col].push({ end, eventId: event.id }); + placed = true; + break; + } + } + if (!placed) { + columns.push([{ end, eventId: event.id }]); + } + } + // Now find the max columns each event actually shares with + // For each event, find all events that overlap it and determine the group width + for (const event of events) { + const start = new Date(event.startTime).getTime(); + const end = new Date(event.endTime).getTime(); + // Count how many columns have events overlapping this time range + let overlappingCols = 0; + for (const col of columns) { + for (const item of col) { + const itemStart = calendarEvents.find(e => e.id === item.eventId); + if (itemStart) { + const iStart = new Date(itemStart.startTime).getTime(); + const iEnd = new Date(itemStart.endTime).getTime(); + if (iStart < end && iEnd > start) { + overlappingCols++; + break; + } + } + } + } + // Find which column this event is in + let eventCol = 0; + for (let col = 0; col < columns.length; col++) { + if (columns[col].some(item => item.eventId === event.id)) { + eventCol = col; + break; + } + } + layout[event.id] = { column: eventCol, totalColumns: Math.max(overlappingCols, 1) }; + } + } + return layout; + }, [calendarEvents]); + // Calculate event duration in pixels for proper height display const getEventDuration = (event: CalendarEvent): number => { if (isAllDayEvent(event)) return 0; // All-day events handled separately @@ -7495,6 +7568,10 @@ export default function WeeklyView() { ? eventColor : "var(--weekly-teal)"; + const overlap = eventOverlapLayout[event.id] || { column: 0, totalColumns: 1 }; + const colWidth = 100 / overlap.totalColumns; + const colLeft = overlap.column * colWidth; + return (
{ - // Try calendar-scoped endpoint first - let response = await fetch(`${GRAPH_ENDPOINT}/me/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${accessToken}` - } + const headers = { 'Authorization': `Bearer ${accessToken}` }; + + // Try non-calendar-scoped endpoint first (more reliable for all ID types) + let response = await fetch(`${GRAPH_ENDPOINT}/me/events/${eventId}`, { + method: 'DELETE', headers }); - if (!response.ok && response.status === 404) { - // Fallback: try non-calendar-scoped endpoint (works better for series master IDs) - response = await fetch(`${GRAPH_ENDPOINT}/me/events/${encodeURIComponent(eventId)}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${accessToken}` - } + if (!response.ok && (response.status === 404 || response.status === 400)) { + // Fallback: try calendar-scoped endpoint + response = await fetch(`${GRAPH_ENDPOINT}/me/calendars/${calendarId}/events/${eventId}`, { + method: 'DELETE', headers }); }