Skip to content

Commit 2e52a5d

Browse files
committed
fix(scheduled-tasks): address review — midnight rollover, stub feedback, smooth Today scroll
- useCalendar: today was frozen at mount, so after midnight the isToday column highlight and the current-time indicator stayed on the previous day. today is now state refreshed by a sleep-resilient minute poll that only re-renders when the calendar day actually changes - CreateTaskModal: the stub submit closed silently, reading as false success; it now shows an info toast that the task was not created - ScheduleCalendar: Today presses scroll smoothly as an orientation cue; mount and scope switches keep instant positioning
1 parent cdb3ae4 commit 2e52a5d

3 files changed

Lines changed: 35 additions & 10 deletions

File tree

apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/create-task-modal/create-task-modal.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ChipModalField,
1313
ChipModalFooter,
1414
ChipModalHeader,
15+
toast,
1516
} from '@/components/emcn'
1617
import type { CalendarSlot } from '@/app/workspace/[workspaceId]/scheduled-tasks/hooks/use-calendar'
1718

@@ -68,8 +69,12 @@ export function CreateTaskModal({ open, onOpenChange, slot, onSubmit }: CreateTa
6869
launchTime,
6970
timezone: DEFAULT_TIMEZONE,
7071
}
71-
if (onSubmit) onSubmit(draft)
72-
else logger.info('Scheduled task draft captured (not persisted this phase)', draft)
72+
if (onSubmit) {
73+
onSubmit(draft)
74+
} else {
75+
logger.info('Scheduled task draft captured (not persisted this phase)', draft)
76+
toast.info('Scheduling is not available yet — this task was not created')
77+
}
7378
close()
7479
}
7580

apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/schedule-calendar/schedule-calendar.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ interface ScheduleCalendarProps {
3737
*
3838
* Scroll behavior: entering week/day scope, and "Today" presses (signaled via an
3939
* internal `scrollSignal`), center the current time in the viewport; month scope
40-
* resets to the top. Plain prev/next navigation never re-centers. Centering is
40+
* resets to the top. Plain prev/next navigation never re-centers. Today presses
41+
* scroll smoothly as an orientation cue; mount and scope switches position
42+
* instantly (animating initial placement would read as a glitch). Centering is
4143
* computed from the time-grid header height plus {@link timeToOffset} rather than
4244
* the now-line element, so it works even on first paint before the line mounts.
4345
*
@@ -57,6 +59,7 @@ export function ScheduleCalendar({
5759
eventsByHour,
5860
}: ScheduleCalendarProps) {
5961
const scrollRef = useRef<HTMLDivElement>(null)
62+
const lastScrollSignalRef = useRef(0)
6063
const [scrollSignal, setScrollSignal] = useState(0)
6164

6265
const grid = useMemo(() => buildCalendarGrid(scope, anchor, today), [scope, anchor, today])
@@ -70,14 +73,17 @@ export function ScheduleCalendar({
7073
useEffect(() => {
7174
const region = scrollRef.current
7275
if (!region) return
76+
const behavior: ScrollBehavior =
77+
scrollSignal !== lastScrollSignalRef.current ? 'smooth' : 'auto'
78+
lastScrollSignalRef.current = scrollSignal
7379
if (scope === 'month') {
74-
region.scrollTo({ top: 0 })
80+
region.scrollTo({ top: 0, behavior })
7581
return
7682
}
7783
const header = region.querySelector('[data-time-grid-header]')
7884
const headerHeight = header ? header.getBoundingClientRect().height : 0
7985
const target = headerHeight + timeToOffset(new Date()) - region.clientHeight / 2
80-
region.scrollTo({ top: Math.max(0, target) })
86+
region.scrollTo({ top: Math.max(0, target), behavior })
8187
}, [scope, scrollSignal])
8288

8389
return (

apps/sim/app/workspace/[workspaceId]/scheduled-tasks/hooks/use-calendar.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
'use client'
22

3-
import { useCallback, useMemo, useState } from 'react'
3+
import { useCallback, useEffect, useState } from 'react'
4+
import { isSameDay } from 'date-fns'
45
import {
56
advanceAnchor,
67
type CalendarScope,
78
} from '@/app/workspace/[workspaceId]/scheduled-tasks/utils/calendar-grid'
89

10+
/** How often to check whether the calendar day has rolled over. */
11+
const DAY_ROLLOVER_POLL_MS = 60_000
12+
913
/** A clicked calendar position: a day, optionally narrowed to an hour slot. */
1014
export interface CalendarSlot {
1115
date: Date
@@ -17,7 +21,7 @@ export interface UseCalendarReturn {
1721
scope: CalendarScope
1822
/** The focused day; week/month ranges derive from it. */
1923
anchor: Date
20-
/** Stable "now" for the calendar's lifetime, shared by every view. */
24+
/** The current calendar day, shared by every view; refreshes at midnight. */
2125
today: Date
2226
selectedSlot: CalendarSlot | null
2327
isCreateOpen: boolean
@@ -32,16 +36,26 @@ export interface UseCalendarReturn {
3236

3337
/**
3438
* Owns the calendar's ephemeral view state (scope, anchor, selected slot, and
35-
* create-modal open state). Pure UI state — `useState`, not a store. All
36-
* mutations are event-driven; there are no effects. Opens on the `week` scope.
39+
* create-modal open state). Pure UI state — `useState`, not a store. Opens on
40+
* the `week` scope. `today` is polled so the today highlight and current-time
41+
* column survive midnight without a remount; the poll only re-renders when the
42+
* day actually changes (the interval is resilient to device sleep, unlike a
43+
* one-shot timeout aimed at midnight).
3744
*/
3845
export function useCalendar(): UseCalendarReturn {
39-
const today = useMemo(() => new Date(), [])
46+
const [today, setToday] = useState<Date>(() => new Date())
4047
const [scope, setScope] = useState<CalendarScope>('week')
4148
const [anchor, setAnchor] = useState<Date>(() => new Date())
4249
const [selectedSlot, setSelectedSlot] = useState<CalendarSlot | null>(null)
4350
const [isCreateOpen, setIsCreateOpen] = useState(false)
4451

52+
useEffect(() => {
53+
const id = setInterval(() => {
54+
setToday((current) => (isSameDay(current, new Date()) ? current : new Date()))
55+
}, DAY_ROLLOVER_POLL_MS)
56+
return () => clearInterval(id)
57+
}, [])
58+
4559
const next = useCallback(() => setAnchor((current) => advanceAnchor(current, scope, 1)), [scope])
4660
const prev = useCallback(() => setAnchor((current) => advanceAnchor(current, scope, -1)), [scope])
4761
const goToday = useCallback(() => setAnchor(new Date()), [])

0 commit comments

Comments
 (0)