@@ -9,12 +9,13 @@ import {
99  Title , 
1010  Badge , 
1111  Anchor , 
12+   Divider , 
1213}  from  "@mantine/core" ; 
1314import  {  useDisclosure  }  from  "@mantine/hooks" ; 
1415import  {  notifications  }  from  "@mantine/notifications" ; 
1516import  {  IconPlus ,  IconTrash  }  from  "@tabler/icons-react" ; 
1617import  dayjs  from  "dayjs" ; 
17- import  React ,  {  useEffect ,  useState  }  from  "react" ; 
18+ import  React ,  {  useEffect ,  useMemo ,   useState  }  from  "react" ; 
1819import  {  useNavigate  }  from  "react-router-dom" ; 
1920import  {  z  }  from  "zod" ; 
2021
@@ -56,96 +57,77 @@ export const ViewEventsPage: React.FC = () => {
5657  const  [ eventList ,  setEventList ]  =  useState < EventsGetResponse > ( [ ] ) ; 
5758  const  api  =  useApi ( "core" ) ; 
5859  const  [ opened ,  {  open,  close } ]  =  useDisclosure ( false ) ; 
59-   const  [ showPrevious ,  {  toggle : togglePrevious  } ]  =  useDisclosure ( false ) ;   // Changed default to false 
60+   const  [ showPrevious ,  {  toggle : togglePrevious  } ]  =  useDisclosure ( false ) ; 
6061  const  [ deleteCandidate ,  setDeleteCandidate ]  = 
6162    useState < EventGetResponse  |  null > ( null ) ; 
6263  const  navigate  =  useNavigate ( ) ; 
6364
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 ] ) ; 
6674
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 ] ) ; 
12694
12795  useEffect ( ( )  =>  { 
12896    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+       } 
148129    } ; 
130+ 
149131    getEvents ( ) ; 
150132  } ,  [ ] ) ; 
151133
@@ -170,6 +152,49 @@ export const ViewEventsPage: React.FC = () => {
170152    } 
171153  } ; 
172154
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+ 
173198  if  ( eventList . length  ===  0 )  { 
174199    return  < FullScreenLoader  /> ; 
175200  } 
@@ -181,6 +206,7 @@ export const ViewEventsPage: React.FC = () => {
181206      < Title  order = { 1 }  mb = "md" > 
182207        Event Management
183208      </ Title > 
209+ 
184210      { deleteCandidate  &&  ( 
185211        < Modal 
186212          opened = { opened } 
@@ -207,6 +233,7 @@ export const ViewEventsPage: React.FC = () => {
207233          </ Group > 
208234        </ Modal > 
209235      ) } 
236+ 
210237      < div 
211238        style = { {  display : "flex" ,  columnGap : "1vw" ,  verticalAlign : "middle"  } } 
212239      > 
@@ -222,6 +249,7 @@ export const ViewEventsPage: React.FC = () => {
222249          { showPrevious  ? "Hide Previous Events"  : "Show Previous Events" } 
223250        </ Button > 
224251      </ div > 
252+ 
225253      < Table 
226254        style = { {  tableLayout : "fixed" ,  width : "100%"  } } 
227255        data-testid = "events-table" 
@@ -237,8 +265,21 @@ export const ViewEventsPage: React.FC = () => {
237265            < Table . Th > Actions</ Table . Th > 
238266          </ Table . Tr > 
239267        </ Table . Thead > 
240-         < Table . Tbody > { eventList . map ( renderTableRow ) } </ Table . Tbody > 
268+         < Table . Tbody > { sortedUpcomingEvents . map ( renderEvent ) } </ Table . Tbody > 
241269      </ 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+       ) } 
242283    </ AuthGuard > 
243284  ) ; 
244285} ; 
0 commit comments