Skip to content

Commit 206a0c4

Browse files
authored
Fix events and room reservation bugs (#141)
* fix events bugs * fix room requests schema * update lockfile * add git blame ignore to postinstall
1 parent d151351 commit 206a0c4

File tree

6 files changed

+221
-349
lines changed

6 files changed

+221
-349
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
],
1010
"packageManager": "[email protected]",
1111
"scripts": {
12+
"postinstall": "npm run setup",
13+
"setup": "git config blame.ignoreRevsFile .git-blame-ignore-revs",
1214
"build": "yarn workspaces run build && yarn lockfile-manage",
1315
"dev": "concurrently --names 'api,ui' 'yarn workspace infra-core-api run dev' 'yarn workspace infra-core-ui run dev'",
1416
"lockfile-manage": "synp --with-workspace --source-file yarn.lock && cp package-lock.json dist/lambda/ && cp package-lock.json dist/sqsConsumer/ && cp src/api/package.lambda.json dist/lambda/package.json && cp src/api/package.lambda.json dist/sqsConsumer/package.json && rm package-lock.json",
@@ -83,4 +85,4 @@
8385
"resolutions": {
8486
"pdfjs-dist": "^4.8.69"
8587
}
86-
}
88+
}

src/api/routes/roomRequests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
283283
const body = {
284284
...request.body,
285285
eventStart: request.body.eventStart.toISOString(),
286-
eventEnd: request.body.eventStart.toISOString(),
286+
eventEnd: request.body.eventEnd.toISOString(),
287287
...(request.body.recurrenceEndDate
288288
? { recurrenceEndDate: request.body.recurrenceEndDate.toISOString() }
289289
: {}),

src/common/types/roomRequest.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,15 @@ export const roomRequestSchema = roomRequestDataSchema
219219
path: ["eventEnd"],
220220
},
221221
)
222+
.refine(
223+
(data) => {
224+
return (data.eventEnd.getTime() - data.eventStart.getTime()) >= (30 * 60 * 1000);
225+
},
226+
{
227+
message: "Event must be at least 30 minutes long",
228+
path: ["eventEnd"],
229+
},
230+
)
222231
.refine(
223232
(data) => {
224233
// If recurrence is enabled, recurrence pattern must be provided

src/ui/pages/events/ManageEvent.page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export const ManageEventPage: React.FC = () => {
9090
const getEvent = async () => {
9191
try {
9292
const response = await api.get(
93-
`/api/v1/events/${eventId}?ts=${Date.now()}`,
93+
`/api/v1/events/${eventId}?ts=${Date.now()}&includeMetadata=true`,
9494
);
9595
const eventData = response.data;
9696

src/ui/pages/events/ViewEvents.page.tsx

Lines changed: 124 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import {
99
Title,
1010
Badge,
1111
Anchor,
12+
Divider,
1213
} from "@mantine/core";
1314
import { useDisclosure } from "@mantine/hooks";
1415
import { notifications } from "@mantine/notifications";
1516
import { IconPlus, IconTrash } from "@tabler/icons-react";
1617
import dayjs from "dayjs";
17-
import React, { useEffect, useState } from "react";
18+
import React, { useEffect, useMemo, useState } from "react";
1819
import { useNavigate } from "react-router-dom";
1920
import { 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

Comments
 (0)