diff --git a/package.json b/package.json index 7e1f9b7..a2b2b61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.4.0", + "version": "1.5.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/app/api/calendar/events/route.ts b/src/app/api/calendar/events/route.ts index 67c2c7f..85ddc89 100644 --- a/src/app/api/calendar/events/route.ts +++ b/src/app/api/calendar/events/route.ts @@ -39,7 +39,7 @@ export async function POST(request: NextRequest) { if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const body = await request.json(); - const { calendarId, title, description, start, end, location, allDay } = body; + const { calendarId, title, description, start, end, location, allDay, recurrence, url } = body; console.log('[API] Creating event:', { calendarId, title, start, end }); @@ -60,8 +60,10 @@ export async function POST(request: NextRequest) { start, end, location, - allDay: !!allDay - }); + allDay: !!allDay, + recurrence, + url, + } as any); // Update cache upsertCachedEvent(userId, connection.id, connection.provider, event) @@ -81,7 +83,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 } = body; + const { calendarId, eventId, title, description, start, end, location, allDay, recurrence, url } = body; console.log('[API] Updating event:', { calendarId, eventId, title }); @@ -102,8 +104,10 @@ export async function PATCH(request: NextRequest) { start, end, location, - allDay: allDay !== undefined ? !!allDay : undefined - }); + allDay: allDay !== undefined ? !!allDay : undefined, + recurrence, + url, + } as any); // Update cache upsertCachedEvent(userId, connection.id, connection.provider, event) diff --git a/src/app/api/tasks/sync/route.ts b/src/app/api/tasks/sync/route.ts index 807f535..2a80e81 100644 --- a/src/app/api/tasks/sync/route.ts +++ b/src/app/api/tasks/sync/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from "@/lib/auth"; import { prisma } from '@/lib/prisma'; -import { createGoogleClient, updateGoogleTaskStatus, updateGoogleTask, deleteGoogleTask, fetchGoogleTasksForSync, GoogleTask } from '@/lib/google-tasks'; +import { createGoogleClient, createGoogleTask, updateGoogleTaskStatus, updateGoogleTask, deleteGoogleTask, fetchGoogleTasksForSync, GoogleTask } from '@/lib/google-tasks'; import { fetchMsTodoTasksForSync, updateMsTodoTask, deleteMsTodoTask, createMsTodoTask, isMsTodoTaskCompleted } from '@/lib/microsoft-todo'; import { getOutlookAccessToken } from '@/lib/outlook-token'; @@ -492,6 +492,35 @@ export async function POST(req: NextRequest) { return NextResponse.json({ success: true, task: updatedTask }); } + if (provider === 'google') { + const account = await prisma.account.findFirst({ + where: { userId: task.userId, provider: 'google' } + }); + if (!account?.access_token) { + return NextResponse.json({ error: 'Google token not available' }, { status: 400 }); + } + + const client = createGoogleClient(account.access_token, account.refresh_token || undefined); + + const created = await createGoogleTask(client, listExternalId, { + title: task.title, + notes: task.description || undefined, + due: task.scheduledDate ? task.scheduledDate.toISOString() : undefined, + }); + + const updatedTask = await prisma.task.update({ + where: { id: taskId }, + data: { + externalId: created.id, + externalProvider: 'google', + externalListId: listExternalId, + lastSyncedAt: new Date(), + } + }); + + return NextResponse.json({ success: true, task: updatedTask }); + } + return NextResponse.json({ error: `Provider "${provider}" creation sync not supported yet` }, { status: 400 }); } catch (error: unknown) { diff --git a/src/app/globals.css b/src/app/globals.css index 3d2f782..3aebe8c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -982,6 +982,27 @@ h3 { flex-wrap: nowrap; } +/* Time-grid tasks: show actions below task text as a dropdown toolbar */ +.time-slot-task > .task-actions { + position: absolute; + top: auto; + bottom: auto; + left: 0; + right: auto; + margin-top: 0; + background: var(--weekly-bg, white); + border: 1px solid var(--weekly-border, #e0e0e0); + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0,0,0,0.12); + padding: 2px 4px; + z-index: 20; +} + +.dark .time-slot-task > .task-actions { + background: #2a2a2a; + border-color: #404040; +} + .task-action-btn { background: none; border: none; diff --git a/src/components/CalendarEventModal.tsx b/src/components/CalendarEventModal.tsx index 00e31f2..70e2b02 100644 --- a/src/components/CalendarEventModal.tsx +++ b/src/components/CalendarEventModal.tsx @@ -32,6 +32,8 @@ export default function CalendarEventModal({ const [title, setTitle] = useState(event?.title || ''); const [description, setDescription] = useState(event?.description || ''); const [location, setLocation] = useState(event?.location || ''); + const [url, setUrl] = useState(event?.url || ''); + const [recurrence, setRecurrence] = useState(event?.recurrence || 'none'); const [calendarId, setCalendarId] = useState(event?.calendarId || (availableCalendars.length > 0 ? availableCalendars[0].id : '')); // Date/Time State @@ -90,6 +92,8 @@ export default function CalendarEventModal({ title, description, location, + url: url || undefined, + recurrence: recurrence !== 'none' ? recurrence : undefined, calendarId, allDay, start: { dateTime: startDate.toISOString() }, @@ -218,6 +222,23 @@ export default function CalendarEventModal({ + {/* Recurrence */} +
+ + +
+ {/* Location */}
@@ -230,9 +251,21 @@ export default function CalendarEventModal({ />
+ {/* URL */} +
+ + setUrl(e.target.value)} + placeholder="https://..." + style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} + /> +
+ {/* Description */}
- +