diff --git a/CHANGELOG.md b/CHANGELOG.md index b0b58bd..96c0011 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project are documented in this file. +## [1.116.3] - 2026-09-05 + +### Added +- Calendar events can now be reassigned to another editable calendar from the event editor. +- Moving an event updates the source and target calendar caches. + +### Changed +- Calendar event backgrounds now use the calendar color with a lighter, readable contrast. +- Google event-specific colors no longer override the selected calendar's color. + ## [1.116.2] - 2026-09-05 ### Fixed diff --git a/package-lock.json b/package-lock.json index 86adcce..aa3bb03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "my-weekly-todo-list", - "version": "1.116.2", + "version": "1.116.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "my-weekly-todo-list", - "version": "1.116.2", + "version": "1.116.3", "license": "MIT", "dependencies": { "@auth/prisma-adapter": "^2.11.1", diff --git a/package.json b/package.json index 30e68b9..581c9f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.116.2", + "version": "1.116.3", "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/app/api/calendar/events/route.ts b/src/app/api/calendar/events/route.ts index b3b8271..5601822 100644 --- a/src/app/api/calendar/events/route.ts +++ b/src/app/api/calendar/events/route.ts @@ -109,7 +109,7 @@ export async function PATCH(request: NextRequest) { if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const body = await request.json(); - const { calendarId, eventId, title, description, start, end, location, allDay, recurrence, recurrenceEndDate, recurrenceCount, recurrenceInterval, recurrenceDays, url, + const { calendarId, originalCalendarId, eventId, title, description, start, end, location, allDay, recurrence, recurrenceEndDate, recurrenceCount, recurrenceInterval, recurrenceDays, timezone, url, reminders, busyStatus, visibility, attendees, attachments, editMode, recurringEventId } = body; console.log('[API] Updating event:', { calendarId, eventId, title, editMode }); @@ -128,6 +128,14 @@ export async function PATCH(request: NextRequest) { const userId = dbUser2?.id; if (!userId) return NextResponse.json({ error: 'User not found' }, { status: 404 }); + const sourceCalendarId = originalCalendarId || calendarId; + const sourceConnection = sourceCalendarId === calendarId + ? connection + : await findConnectionForCalendar(session.user.email, sourceCalendarId); + if (!sourceConnection) { + return NextResponse.json({ error: 'Original calendar connection not found' }, { status: 404 }); + } + // For "all events" edit mode on recurring events, use the series master ID let targetEventId = eventId; if (editMode === 'all' && recurringEventId) { @@ -139,25 +147,35 @@ export async function PATCH(request: NextRequest) { } } - const event = await updateCalendarEvent(connection, calendarId, targetEventId, { - title, - description, - start, - end, - location, + const eventData = { + title, description, start, end, location, allDay: allDay !== undefined ? !!allDay : undefined, - recurrence, - recurrenceEndDate, - recurrenceCount, - recurrenceInterval, - recurrenceDays, - url, - reminders, - busyStatus, - visibility, - attendees, - attachments, - } as any); + recurrence, recurrenceEndDate, recurrenceCount, recurrenceInterval, recurrenceDays, timezone, + url, reminders, busyStatus, visibility, attendees, attachments, + } as any; + + // Providers do not share a portable move operation: create in the target, + // then remove the original event from its source calendar. + if (sourceCalendarId !== calendarId) { + if (editMode && editMode !== 'all') { + return NextResponse.json({ error: 'Recurring event instances must be moved as a whole series' }, { status: 400 }); + } + const movedEvent = await createCalendarEvent(connection, calendarId, eventData); + if (!movedEvent.backgroundColor && connection.calendars) { + const cal = (connection.calendars as any[]).find((c: any) => c.id === calendarId); + if (cal?.backgroundColor || cal?.color) movedEvent.backgroundColor = cal.backgroundColor || cal.color; + } + await deleteCalendarEvent(sourceConnection, sourceCalendarId, targetEventId, 'all'); + try { + await deleteCachedEvent(userId, targetEventId, sourceConnection.provider); + await upsertCachedEvent(userId, connection.id, connection.provider, movedEvent); + } catch (e) { + console.error('[CACHE] Failed to update cache after moving event:', e); + } + return NextResponse.json({ event: movedEvent }); + } + + const event = await updateCalendarEvent(connection, calendarId, targetEventId, eventData); // Attach calendar color from connection metadata for cache + frontend if (!event.backgroundColor && connection.calendars) { diff --git a/src/components/CalendarEventModal.tsx b/src/components/CalendarEventModal.tsx index 30ee5d8..6127bfc 100644 --- a/src/components/CalendarEventModal.tsx +++ b/src/components/CalendarEventModal.tsx @@ -364,6 +364,7 @@ export default function CalendarEventModal({ recurrenceEndDate: recurrence !== 'none' && recurrenceEndType === 'date' && recurrenceEndDateValue ? recurrenceEndDateValue : undefined, recurrenceCount: recurrence !== 'none' && recurrenceEndType === 'count' && recurrenceCount > 0 ? recurrenceCount : undefined, calendarId, + ...(event?.calendarId ? { originalCalendarId: event.calendarId } : {}), allDay, start: { dateTime: startDate.toISOString() }, end: { dateTime: endDate.toISOString() }, @@ -601,22 +602,22 @@ export default function CalendarEventModal({