Skip to content

Commit 8166b6c

Browse files
authored
Merge pull request #62 from novaforgood/fix-calendar-loading
refactor event calendar so loading spinner is in the page
2 parents 89a2fcf + c971366 commit 8166b6c

2 files changed

Lines changed: 97 additions & 61 deletions

File tree

icv/src/app/(nav)/calendar/page.tsx

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,33 @@
22

33
import ScheduledCheckInCreation from '@/app/_components/calendar/ScheduledCheckInCreation'
44
import EventsCalendar from '@/app/_components/calendar/EventsCalendar'
5-
import { useState } from 'react'
5+
import { useCallback, useState } from 'react'
66

77
const Page = () => {
8-
const [newEvents, setNewEvents] = useState(false)
8+
const [newEvents, setNewEvents] = useState(false)
99

10-
return (
11-
<div className="relative min-h-screen m-[48px] overflow-x-hidden">
12-
{/* Full calendar view */}
13-
<EventsCalendar
14-
onReloadEvents = {() => setNewEvents(false)}
15-
newEvents = {newEvents}
16-
/>
10+
const handleReloadEvents = useCallback(() => {
11+
setNewEvents(false)
12+
}, [])
1713

18-
{/* Floating button overlaid on calendar */}
19-
<div className="fixed bottom-6 right-6 z-50">
20-
<ScheduledCheckInCreation
21-
onNewEvent = {() => setNewEvents(true)}
22-
/>
23-
</div>
24-
</div>
25-
)
14+
const handleNewEvent = useCallback(() => {
15+
setNewEvents(true)
16+
}, [])
17+
18+
return (
19+
<div className="relative m-[48px] min-h-screen overflow-x-hidden">
20+
{/* Full calendar view */}
21+
<EventsCalendar
22+
onReloadEvents={handleReloadEvents}
23+
newEvents={newEvents}
24+
/>
25+
26+
{/* Floating button overlaid on calendar */}
27+
<div className="fixed bottom-6 right-6 z-50">
28+
<ScheduledCheckInCreation onNewEvent={handleNewEvent} />
29+
</div>
30+
</div>
31+
)
2632
}
2733

2834
export default Page

icv/src/app/_components/calendar/EventsCalendar.tsx

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,14 @@ interface EventsCalendarProps {
200200
onReloadEvents: () => void
201201
}
202202

203+
type ScheduleType = 'my' | 'team'
204+
203205
const EventsCalendar: React.FC<EventsCalendarProps> = ({
204206
newEvents,
205207
onReloadEvents,
206208
}) => {
207209
const isSmallScreen = useIsSmallScreen()
208-
const [scheduleType, setScheduleType] = useState<'my' | 'team'>('my')
210+
const [scheduleType, setScheduleType] = useState<ScheduleType>('my')
209211
const [hideWeekends, setHideWeekends] = useState(false)
210212
const [rawEvents, setRawEvents] = useState<CheckInType[]>([])
211213
const [currentDate, setCurrentDate] = useState(new Date())
@@ -239,6 +241,8 @@ const EventsCalendar: React.FC<EventsCalendarProps> = ({
239241
useEffect(() => {
240242
const fetchEvents = async () => {
241243
setLoading(true)
244+
setError('')
245+
242246
try {
243247
const data =
244248
scheduleType === 'my' && assigneeId
@@ -247,6 +251,7 @@ const EventsCalendar: React.FC<EventsCalendarProps> = ({
247251
setRawEvents(data)
248252
} catch (err) {
249253
console.error('Failed to fetch events:', err)
254+
setError('Error loading events')
250255
} finally {
251256
setLoading(false)
252257
}
@@ -258,7 +263,7 @@ const EventsCalendar: React.FC<EventsCalendarProps> = ({
258263

259264
setReloadEvents(false)
260265
onReloadEvents()
261-
}, [scheduleType, assigneeId, reloadEvents, newEvents])
266+
}, [scheduleType, assigneeId, reloadEvents, newEvents, onReloadEvents])
262267

263268
const events = useMemo(() => {
264269
if (!rawEvents) return []
@@ -278,10 +283,6 @@ const EventsCalendar: React.FC<EventsCalendarProps> = ({
278283
}))
279284
}, [rawEvents])
280285

281-
if (loading) return <div className="p-4">Loading calendar...</div>
282-
if (error)
283-
return <div className="p-4 text-red-500">Error loading events</div>
284-
285286
const CustomToolbar = (toolbar: any) => {
286287
const goToBack = () => {
287288
toolbar.onNavigate('PREV')
@@ -385,7 +386,11 @@ const EventsCalendar: React.FC<EventsCalendarProps> = ({
385386
{/* Toggle - row below title on screens smaller than lg, same width at all breakpoints */}
386387
<div className="relative inline-flex w-fit items-center justify-start self-start rounded-[20px] bg-zinc-200 p-1 lg:self-center">
387388
<div
388-
className={`absolute transition-all duration-300 ease-in-out ${scheduleType === 'my' ? 'left-1' : 'left-[calc(100%-50%-4px)]'} h-[calc(100%-8px)] w-[calc(50%-4px)] rounded-[16px] bg-black`}
389+
className={`absolute inset-y-1 left-1 w-[calc(50%-4px)] rounded-[16px] bg-black transition-transform duration-300 ease-out ${
390+
scheduleType === 'team'
391+
? 'translate-x-full'
392+
: 'translate-x-0'
393+
}`}
389394
/>
390395
<button
391396
onClick={() => setScheduleType('my')}
@@ -413,43 +418,68 @@ const EventsCalendar: React.FC<EventsCalendarProps> = ({
413418
</div>
414419

415420
{/* Calendar */}
416-
<div className="h-[calc(100vh-12rem)] min-h-[400px] overflow-hidden [&_.rbc-header]:border-b-0 [&_.rbc-header]:bg-gray-50 [&_.rbc-header]:py-3 [&_.rbc-header]:font-medium [&_.rbc-time-content]:border-t [&_.rbc-time-content]:border-gray-200 [&_.rbc-time-gutter_.rbc-timeslot-group]:border-r [&_.rbc-time-header]:border-gray-200 [&_.rbc-timeslot-group]:border-gray-200">
417-
<Calendar<any>
418-
localizer={localizer}
419-
events={events}
420-
view={hideWeekends ? Views.WORK_WEEK : Views.WEEK}
421-
defaultView={Views.WEEK}
422-
views={{
423-
week: (isSmallScreen ? ThreeDayView : true) as any,
424-
work_week: (isSmallScreen
425-
? ThreeDayWorkWeekView
426-
: true) as any,
427-
}}
428-
date={currentDate}
429-
onNavigate={(date) => setCurrentDate(date)}
430-
startAccessor="start"
431-
endAccessor="end"
432-
style={{ height: '100%' }}
433-
className="[&_.rbc-event-label]:hidden [&_.rbc-timeslot-group]:!min-h-[100px]"
434-
timeslots={1}
435-
step={60}
436-
defaultDate={new Date()}
437-
scrollToTime={scrollTime}
438-
tooltipAccessor="location"
439-
onSelectEvent={(event) => setSelectedEvent(event)}
440-
components={{
441-
toolbar: CustomToolbar,
442-
event: CustomEvent,
443-
}}
444-
eventPropGetter={(event) => ({
445-
className: 'rounded-md border-none bg-background',
446-
style: {
447-
backgroundColor: event.assigneeId
448-
? getUserColor(event.assigneeId)
449-
: '#4EA0C9',
450-
},
451-
})}
452-
/>
421+
<div className="relative h-[calc(100vh-12rem)] min-h-[400px] overflow-hidden [&_.rbc-header]:border-b-0 [&_.rbc-header]:bg-gray-50 [&_.rbc-header]:py-3 [&_.rbc-header]:font-medium [&_.rbc-time-content]:border-t [&_.rbc-time-content]:border-gray-200 [&_.rbc-time-gutter_.rbc-timeslot-group]:border-r [&_.rbc-time-header]:border-gray-200 [&_.rbc-timeslot-group]:border-gray-200">
422+
{error ? (
423+
<div className="flex h-full items-center justify-center rounded-xl border border-red-200 bg-red-50 px-4 text-red-600">
424+
{error}
425+
</div>
426+
) : (
427+
<>
428+
<div
429+
className={`h-full transition-opacity duration-200 ${
430+
loading ? 'pointer-events-none opacity-40' : ''
431+
}`}
432+
>
433+
<Calendar<any>
434+
localizer={localizer}
435+
events={events}
436+
view={hideWeekends ? Views.WORK_WEEK : Views.WEEK}
437+
defaultView={Views.WEEK}
438+
views={{
439+
week: (isSmallScreen
440+
? ThreeDayView
441+
: true) as any,
442+
work_week: (isSmallScreen
443+
? ThreeDayWorkWeekView
444+
: true) as any,
445+
}}
446+
date={currentDate}
447+
onNavigate={(date) => setCurrentDate(date)}
448+
startAccessor="start"
449+
endAccessor="end"
450+
style={{ height: '100%' }}
451+
className="[&_.rbc-event-label]:hidden [&_.rbc-timeslot-group]:!min-h-[100px]"
452+
timeslots={1}
453+
step={60}
454+
defaultDate={new Date()}
455+
scrollToTime={scrollTime}
456+
tooltipAccessor="location"
457+
onSelectEvent={(event) => setSelectedEvent(event)}
458+
components={{
459+
toolbar: CustomToolbar,
460+
event: CustomEvent,
461+
}}
462+
eventPropGetter={(event) => ({
463+
className: 'rounded-md border-none bg-background',
464+
style: {
465+
backgroundColor: event.assigneeId
466+
? getUserColor(event.assigneeId)
467+
: '#4EA0C9',
468+
},
469+
})}
470+
/>
471+
</div>
472+
473+
{loading && (
474+
<div className="absolute inset-0 z-10 flex items-center justify-center bg-background/70 backdrop-blur-[1px]">
475+
<div className="flex items-center gap-3 rounded-full border border-border bg-background px-4 py-2 text-sm font-medium text-foreground shadow-sm">
476+
<span className="h-4 w-4 animate-spin rounded-full border-2 border-foreground/20 border-t-foreground" />
477+
Loading calendar...
478+
</div>
479+
</div>
480+
)}
481+
</>
482+
)}
453483
</div>
454484

455485
<EditScheduledCheckIn

0 commit comments

Comments
 (0)