@@ -9,12 +9,13 @@ import {
9
9
Title ,
10
10
Badge ,
11
11
Anchor ,
12
+ Divider ,
12
13
} from "@mantine/core" ;
13
14
import { useDisclosure } from "@mantine/hooks" ;
14
15
import { notifications } from "@mantine/notifications" ;
15
16
import { IconPlus , IconTrash } from "@tabler/icons-react" ;
16
17
import dayjs from "dayjs" ;
17
- import React , { useEffect , useState } from "react" ;
18
+ import React , { useEffect , useMemo , useState } from "react" ;
18
19
import { useNavigate } from "react-router-dom" ;
19
20
import { z } from "zod" ;
20
21
@@ -56,96 +57,77 @@ export const ViewEventsPage: React.FC = () => {
56
57
const [ eventList , setEventList ] = useState < EventsGetResponse > ( [ ] ) ;
57
58
const api = useApi ( "core" ) ;
58
59
const [ opened , { open, close } ] = useDisclosure ( false ) ;
59
- const [ showPrevious , { toggle : togglePrevious } ] = useDisclosure ( false ) ; // Changed default to false
60
+ const [ showPrevious , { toggle : togglePrevious } ] = useDisclosure ( false ) ;
60
61
const [ deleteCandidate , setDeleteCandidate ] =
61
62
useState < EventGetResponse | null > ( null ) ;
62
63
const navigate = useNavigate ( ) ;
63
64
64
- const renderTableRow = ( event : EventGetResponse ) => {
65
- const shouldShow = event . upcoming || ( ! event . upcoming && showPrevious ) ;
65
+ // Use useMemo to sort and filter events only when dependencies change
66
+ const sortedUpcomingEvents = useMemo ( ( ) => {
67
+ return eventList
68
+ . filter ( ( event : EventGetResponse ) => event . upcoming )
69
+ . sort (
70
+ ( a : EventGetResponse , b : EventGetResponse ) =>
71
+ new Date ( a . start ) . getTime ( ) - new Date ( b . start ) . getTime ( ) ,
72
+ ) ;
73
+ } , [ eventList ] ) ;
66
74
67
- return (
68
- < Transition
69
- mounted = { shouldShow }
70
- transition = "fade"
71
- duration = { 400 }
72
- timingFunction = "ease"
73
- key = { `${ event . id } -tr-transition` }
74
- >
75
- { ( styles ) => (
76
- < tr
77
- style = { { ...styles , display : shouldShow ? "table-row" : "none" } }
78
- key = { `${ event . id } -tr` }
79
- >
80
- < Table . Td >
81
- { event . title } { " " }
82
- { event . featured ? < Badge color = "green" > Featured</ Badge > : null }
83
- </ Table . Td >
84
- < Table . Td >
85
- { dayjs ( event . start ) . format ( "MMM D YYYY hh:mm A" ) }
86
- </ Table . Td >
87
- < Table . Td >
88
- { event . end
89
- ? dayjs ( event . end ) . format ( "MMM D YYYY hh:mm A" )
90
- : "N/A" }
91
- </ Table . Td >
92
- < Table . Td >
93
- { event . locationLink ? (
94
- < Anchor target = "_blank" size = "sm" href = { event . locationLink } >
95
- { event . location }
96
- </ Anchor >
97
- ) : (
98
- event . location
99
- ) }
100
- </ Table . Td >
101
- < Table . Td > { event . host } </ Table . Td >
102
- < Table . Td >
103
- { capitalizeFirstLetter ( event . repeats || "Never" ) }
104
- </ Table . Td >
105
- < Table . Td >
106
- < ButtonGroup >
107
- < Button component = "a" href = { `/events/edit/${ event . id } ` } >
108
- Edit
109
- </ Button >
110
- < Button
111
- color = "red"
112
- onClick = { ( ) => {
113
- setDeleteCandidate ( event ) ;
114
- open ( ) ;
115
- } }
116
- >
117
- Delete
118
- </ Button >
119
- </ ButtonGroup >
120
- </ Table . Td >
121
- </ tr >
122
- ) }
123
- </ Transition >
124
- ) ;
125
- } ;
75
+ // Use useMemo to sort and filter previous events only when dependencies change
76
+ const sortedPreviousEvents = useMemo ( ( ) => {
77
+ return eventList
78
+ . filter ( ( event : EventGetResponse ) => ! event . upcoming )
79
+ . sort ( ( a : EventGetResponse , b : EventGetResponse ) => {
80
+ // For repeating events, compare by repeatEnds date first (if available)
81
+ if ( a . repeatEnds && b . repeatEnds ) {
82
+ return (
83
+ new Date ( b . repeatEnds ) . getTime ( ) - new Date ( a . repeatEnds ) . getTime ( )
84
+ ) ;
85
+ } else if ( a . repeatEnds ) {
86
+ return - 1 ; // a has repeatEnds, b doesn't, so a comes first
87
+ } else if ( b . repeatEnds ) {
88
+ return 1 ; // b has repeatEnds, a doesn't, so b comes first
89
+ }
90
+ // Otherwise sort by start date in reverse order (newest first)
91
+ return new Date ( b . start ) . getTime ( ) - new Date ( a . start ) . getTime ( ) ;
92
+ } ) ;
93
+ } , [ eventList ] ) ;
126
94
127
95
useEffect ( ( ) => {
128
96
const getEvents = async ( ) => {
129
- // setting ts lets us tell cloudfront I want fresh data
130
- const response = await api . get ( `/api/v1/events?ts=${ Date . now ( ) } ` ) ;
131
- const upcomingEvents = await api . get (
132
- `/api/v1/events?upcomingOnly=true&ts=${ Date . now ( ) } ` ,
133
- ) ;
134
- const upcomingEventsSet = new Set (
135
- upcomingEvents . data . map ( ( x : EventGetResponse ) => x . id ) ,
136
- ) ;
137
- const events = response . data ;
138
- events . sort ( ( a : EventGetResponse , b : EventGetResponse ) => {
139
- return a . start . localeCompare ( b . start ) ;
140
- } ) ;
141
- const enrichedResponse = response . data . map ( ( item : EventGetResponse ) => {
142
- if ( upcomingEventsSet . has ( item . id ) ) {
143
- return { ...item , upcoming : true } ;
144
- }
145
- return { ...item , upcoming : false } ;
146
- } ) ;
147
- setEventList ( enrichedResponse ) ;
97
+ try {
98
+ // Setting ts lets us tell cloudfront I want fresh data
99
+ const response = await api . get ( `/api/v1/events?ts=${ Date . now ( ) } ` ) ;
100
+ const upcomingEvents = await api . get (
101
+ `/api/v1/events?upcomingOnly=true&ts=${ Date . now ( ) } ` ,
102
+ ) ;
103
+
104
+ const upcomingEventsSet = new Set (
105
+ upcomingEvents . data . map ( ( x : EventGetResponse ) => x . id ) ,
106
+ ) ;
107
+
108
+ const events = response . data ;
109
+ events . sort ( ( a : EventGetResponse , b : EventGetResponse ) => {
110
+ return a . start . localeCompare ( b . start ) ;
111
+ } ) ;
112
+
113
+ const enrichedResponse = response . data . map ( ( item : EventGetResponse ) => {
114
+ if ( upcomingEventsSet . has ( item . id ) ) {
115
+ return { ...item , upcoming : true } ;
116
+ }
117
+ return { ...item , upcoming : false } ;
118
+ } ) ;
119
+
120
+ setEventList ( enrichedResponse ) ;
121
+ } catch ( error ) {
122
+ console . error ( "Error fetching events:" , error ) ;
123
+ notifications . show ( {
124
+ title : "Error fetching events" ,
125
+ message : `${ error } ` ,
126
+ color : "red" ,
127
+ } ) ;
128
+ }
148
129
} ;
130
+
149
131
getEvents ( ) ;
150
132
} , [ ] ) ;
151
133
@@ -170,6 +152,49 @@ export const ViewEventsPage: React.FC = () => {
170
152
}
171
153
} ;
172
154
155
+ const renderEvent = ( event : EventGetResponse ) => {
156
+ return (
157
+ < tr key = { `${ event . id } -tr` } >
158
+ < Table . Td >
159
+ { event . title } { " " }
160
+ { event . featured ? < Badge color = "green" > Featured</ Badge > : null }
161
+ </ Table . Td >
162
+ < Table . Td > { dayjs ( event . start ) . format ( "MMM D YYYY hh:mm A" ) } </ Table . Td >
163
+ < Table . Td >
164
+ { event . end ? dayjs ( event . end ) . format ( "MMM D YYYY hh:mm A" ) : "N/A" }
165
+ </ Table . Td >
166
+ < Table . Td >
167
+ { event . locationLink ? (
168
+ < Anchor target = "_blank" size = "sm" href = { event . locationLink } >
169
+ { event . location }
170
+ </ Anchor >
171
+ ) : (
172
+ event . location
173
+ ) }
174
+ </ Table . Td >
175
+ < Table . Td > { event . host } </ Table . Td >
176
+ < Table . Td > { capitalizeFirstLetter ( event . repeats || "Never" ) } </ Table . Td >
177
+ < Table . Td >
178
+ < ButtonGroup >
179
+ < Button component = "a" href = { `/events/edit/${ event . id } ` } >
180
+ Edit
181
+ </ Button >
182
+ < Button
183
+ color = "red"
184
+ onClick = { ( e ) => {
185
+ e . stopPropagation ( ) ;
186
+ setDeleteCandidate ( event ) ;
187
+ open ( ) ;
188
+ } }
189
+ >
190
+ Delete
191
+ </ Button >
192
+ </ ButtonGroup >
193
+ </ Table . Td >
194
+ </ tr >
195
+ ) ;
196
+ } ;
197
+
173
198
if ( eventList . length === 0 ) {
174
199
return < FullScreenLoader /> ;
175
200
}
@@ -181,6 +206,7 @@ export const ViewEventsPage: React.FC = () => {
181
206
< Title order = { 1 } mb = "md" >
182
207
Event Management
183
208
</ Title >
209
+
184
210
{ deleteCandidate && (
185
211
< Modal
186
212
opened = { opened }
@@ -207,6 +233,7 @@ export const ViewEventsPage: React.FC = () => {
207
233
</ Group >
208
234
</ Modal >
209
235
) }
236
+
210
237
< div
211
238
style = { { display : "flex" , columnGap : "1vw" , verticalAlign : "middle" } }
212
239
>
@@ -222,6 +249,7 @@ export const ViewEventsPage: React.FC = () => {
222
249
{ showPrevious ? "Hide Previous Events" : "Show Previous Events" }
223
250
</ Button >
224
251
</ div >
252
+
225
253
< Table
226
254
style = { { tableLayout : "fixed" , width : "100%" } }
227
255
data-testid = "events-table"
@@ -237,8 +265,21 @@ export const ViewEventsPage: React.FC = () => {
237
265
< Table . Th > Actions</ Table . Th >
238
266
</ Table . Tr >
239
267
</ Table . Thead >
240
- < Table . Tbody > { eventList . map ( renderTableRow ) } </ Table . Tbody >
268
+ < Table . Tbody > { sortedUpcomingEvents . map ( renderEvent ) } </ Table . Tbody >
241
269
</ Table >
270
+ { showPrevious && (
271
+ < >
272
+ < Divider labelPosition = "center" label = "Previous Events" />
273
+ < Table
274
+ style = { { tableLayout : "fixed" , width : "100%" } }
275
+ data-testid = "events-previous-table"
276
+ >
277
+ < Table . Tbody >
278
+ { showPrevious && sortedPreviousEvents . map ( renderEvent ) }
279
+ </ Table . Tbody >
280
+ </ Table >
281
+ </ >
282
+ ) }
242
283
</ AuthGuard >
243
284
) ;
244
285
} ;
0 commit comments