diff --git a/package.json b/package.json index dd2fc65..ad03163 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.13.1", + "version": "1.14.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/tasks/route.ts b/src/app/api/tasks/route.ts index dda7c1b..6f7f6e2 100644 --- a/src/app/api/tasks/route.ts +++ b/src/app/api/tasks/route.ts @@ -252,6 +252,49 @@ export async function POST(request: NextRequest) { isRolling = user.autoRolling || false; } + // Check if someday list is linked to an external provider + let externalId: string | null = null; + let externalProvider: string | null = null; + let externalListId: string | null = null; + + if (somedayListId) { + const somedayList = await prisma.somedayList.findUnique({ + where: { id: somedayListId }, + select: { externalId: true, externalProvider: true }, + }); + + if (somedayList?.externalId && somedayList?.externalProvider) { + try { + if (somedayList.externalProvider === 'google') { + const { createGoogleClient, createGoogleTask } = await import('@/lib/google-tasks'); + const account = await prisma.account.findFirst({ + where: { userId, provider: { in: ['google-calendar', 'google'] } }, + }); + if (account?.access_token) { + const client = createGoogleClient(account.access_token, account.refresh_token || undefined); + const gTask = await createGoogleTask(client, somedayList.externalId, { title }); + externalId = gTask.id; + externalProvider = 'google'; + externalListId = somedayList.externalId; + } + } else if (somedayList.externalProvider === 'outlook') { + const { createMsTodoTask } = await import('@/lib/microsoft-todo'); + const { getOutlookAccessToken } = await import('@/lib/outlook-token'); + const accessToken = await getOutlookAccessToken(userId); + if (accessToken) { + const msTask = await createMsTodoTask(accessToken, somedayList.externalId, { title }); + externalId = msTask.id; + externalProvider = 'outlook'; + externalListId = somedayList.externalId; + } + } + } catch (syncError) { + console.error('Failed to sync new task to external provider:', syncError); + // Continue creating locally even if sync fails + } + } + } + const task = await prisma.task.create({ data: { title, @@ -273,6 +316,7 @@ export async function POST(request: NextRequest) { somedaySlotIndex: somedaySlotIndex !== undefined ? parseInt(somedaySlotIndex) : null, parentTaskId: parentTaskId || null, ...(projectId !== undefined && { projectId: projectId || null }), + ...(externalId && { externalId, externalProvider, externalListId }), }, }); diff --git a/src/app/globals.css b/src/app/globals.css index 7ae3b19..728bac3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1453,7 +1453,6 @@ h3 { .weekly-someday-list-title-input { font-size: 1.15rem; font-weight: 800; - text-transform: uppercase; letter-spacing: 0.08em; color: var(--weekly-text, #222); background: transparent; diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 4af7d74..1faf776 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -1012,6 +1012,7 @@ export default function WeeklyView() { const isScrollSyncing = useRef(false); const dayHeaderRef = useRef(null); const somedayGridRef = useRef(null); + const somedaySectionRef = useRef(null); // Scroll sync handler const handleTimeColumnScroll = (e: React.UIEvent) => { @@ -1477,40 +1478,42 @@ export default function WeeklyView() { - useEffect(() => { - const el = somedayGridRef.current; - if (!el) return; + // Horizontal scroll: convert vertical wheel to horizontal in someday area + // Callback ref ensures handler is attached as soon as element mounts + const somedayWheelCleanup = useRef<(() => void) | null>(null); + const somedaySectionRefCb = useCallback((node: HTMLElement | null) => { + // Cleanup previous + if (somedayWheelCleanup.current) { + somedayWheelCleanup.current(); + somedayWheelCleanup.current = null; + } + somedaySectionRef.current = node; + if (!node) return; + const handler = (e: WheelEvent) => { - // Ignore if scrolling horizontally natively (trackpad) + const grid = somedayGridRef.current; + if (!grid) return; + + // Let native horizontal scroll (trackpad) pass through if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return; + if (e.deltaY === 0) return; - // Check if hovering over a vertically scrollable list that isn't at its boundary - let target = e.target as HTMLElement | null; - let canScrollVertically = false; + // Only convert if grid has horizontal overflow + if (grid.scrollWidth <= grid.clientWidth + 1) return; - while (target && target !== el) { - if (target.scrollHeight > target.clientHeight) { - const style = window.getComputedStyle(target); - if (style.overflowY === 'auto' || style.overflowY === 'scroll') { - const atTop = target.scrollTop <= 0; - const atBottom = target.scrollTop + target.clientHeight >= target.scrollHeight - 1; - if (!(e.deltaY < 0 && atTop) && !(e.deltaY > 0 && atBottom)) { - canScrollVertically = true; - break; - } - } - } - target = target.parentElement; - } + // Check boundaries - allow page scroll when at edges + const atLeft = grid.scrollLeft <= 0; + const atRight = grid.scrollLeft + grid.clientWidth >= grid.scrollWidth - 1; + if (e.deltaY < 0 && atLeft) return; + if (e.deltaY > 0 && atRight) return; - if (!canScrollVertically && e.deltaY !== 0) { - e.preventDefault(); - el.scrollLeft += e.deltaY; - } + e.preventDefault(); + grid.scrollLeft += e.deltaY; }; - el.addEventListener("wheel", handler, { passive: false }); - return () => el.removeEventListener("wheel", handler); - }, [showSomeday, somedayExpanded]); + + node.addEventListener("wheel", handler, { passive: false }); + somedayWheelCleanup.current = () => node.removeEventListener("wheel", handler); + }, []); const saveSetting = async (key: string, value: any) => { // Per-device settings: save to cookie ONLY (not DB) so each device keeps its own value if (DEVICE_SETTINGS_KEYS.includes(key)) { @@ -3511,31 +3514,6 @@ export default function WeeklyView() { setDropPreview(null); }; - // Added: Horizontal scroll with mouse wheel for someday area - useEffect(() => { - const somedayGrid = somedayGridRef.current; - if (!somedayGrid) return; - - const handleWheel = (e: WheelEvent) => { - // Only handle if there is horizontal overflow - if (somedayGrid.scrollWidth > somedayGrid.clientWidth) { - // Dominant vertical scroll -> horizontal scroll - if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) { - // Check if we can scroll further in the requested direction - const isAtLeft = somedayGrid.scrollLeft === 0; - const isAtRight = Math.abs(somedayGrid.scrollWidth - somedayGrid.clientWidth - somedayGrid.scrollLeft) < 1; - - if ((e.deltaY < 0 && !isAtLeft) || (e.deltaY > 0 && !isAtRight)) { - e.preventDefault(); - somedayGrid.scrollLeft += e.deltaY; - } - } - } - }; - - somedayGrid.addEventListener("wheel", handleWheel, { passive: false }); - return () => somedayGrid.removeEventListener("wheel", handleWheel); - }, [somedayExpanded]); const handleSomedayDragOver = (e: React.DragEvent, listId: string, slotIdx: number) => { e.preventDefault(); @@ -5345,6 +5323,7 @@ export default function WeeklyView() { {/* Someday Section */} {showSomeday && (