@@ -41,7 +24,7 @@ const WrappedAdminBookingCreationPopUp = ({
bookingSlots={bookingSlots}
handleClose={handleClose}
isPending={isPending}
- isLoading={hasNextPage}
+ isLoading={stillLoadingUsers}
bookingCreationHandler={async (startDate, endDate, uid) => {
await handleAddUserToBooking({ startDate, endDate, userId: uid })
}}
diff --git a/client/src/components/composite/Admin/AdminNavbar/AdminNavbar.tsx b/client/src/components/composite/Admin/AdminNavbar/AdminNavbar.tsx
index 24569dfd9..d167527b5 100644
--- a/client/src/components/composite/Admin/AdminNavbar/AdminNavbar.tsx
+++ b/client/src/components/composite/Admin/AdminNavbar/AdminNavbar.tsx
@@ -30,6 +30,9 @@ const AdminNavbar = () => {
availability
+
+ history
+
diff --git a/client/src/hooks/useAllUsers.ts b/client/src/hooks/useAllUsers.ts
new file mode 100644
index 000000000..0b21f6ddd
--- /dev/null
+++ b/client/src/hooks/useAllUsers.ts
@@ -0,0 +1,35 @@
+import { useUsersQuery } from "@/services/Admin/AdminQueries"
+import { useEffect, useMemo } from "react"
+
+/**
+ * A wrapper hook for {@link useUsersQuery} to be used by consumers which
+ * require all of the user data, and do not care about manually fetching
+ *
+ * @example
+ * const {users, stillLoadingUsers} = useAllUsers()
+ *
+ * if(!stillLoadingUsers){
+ * performFunctionRequiringAllUsers(users)
+ * }
+ */
+export default function useAllUsers() {
+ const {
+ data: userPages,
+ fetchNextPage,
+ isFetchingNextPage,
+ hasNextPage
+ } = useUsersQuery()
+
+ useEffect(() => {
+ if (hasNextPage && !isFetchingNextPage) {
+ fetchNextPage()
+ }
+ }, [fetchNextPage, isFetchingNextPage, hasNextPage])
+
+ const users = useMemo(
+ () => userPages?.pages.flatMap((page) => page.data || []),
+ [userPages]
+ )
+
+ return { users, stillLoadingUsers: hasNextPage }
+}
diff --git a/client/src/models/History.ts b/client/src/models/History.ts
new file mode 100644
index 000000000..b3e532904
--- /dev/null
+++ b/client/src/models/History.ts
@@ -0,0 +1,3 @@
+import { components } from "./__generated__/schema"
+
+export type BookingHistoryEvent = components["schemas"]["BookingHistoryEvent"]
diff --git a/client/src/models/__generated__/schema.d.ts b/client/src/models/__generated__/schema.d.ts
index 1008eacb9..00518f901 100644
--- a/client/src/models/__generated__/schema.d.ts
+++ b/client/src/models/__generated__/schema.d.ts
@@ -134,7 +134,7 @@ export interface paths {
};
"/admin/bookings/history": {
/** @description Fetches the **latest** booking history events (uses cursor-based pagination) */
- post: operations["GetLatestHistory"];
+ get: operations["GetLatestHistory"];
};
}
@@ -591,12 +591,6 @@ export interface components {
message?: string;
historyEvents?: components["schemas"]["BookingHistoryEvent"][];
};
- FetchLatestBookingEventRequest: {
- /** Format: double */
- limit: number;
- /** @description The id of the cursor to continue paginating from */
- cursor?: string;
- };
};
responses: {
};
@@ -1037,10 +1031,10 @@ export interface operations {
};
/** @description Fetches the **latest** booking history events (uses cursor-based pagination) */
GetLatestHistory: {
- /** @description - contains the pagination variables */
- requestBody: {
- content: {
- "application/json": components["schemas"]["FetchLatestBookingEventRequest"];
+ parameters: {
+ query: {
+ limit: number;
+ cursor?: string;
};
};
responses: {
diff --git a/client/src/services/Admin/AdminMutations.ts b/client/src/services/Admin/AdminMutations.ts
index 4c8f9e75f..e608fde8a 100644
--- a/client/src/services/Admin/AdminMutations.ts
+++ b/client/src/services/Admin/AdminMutations.ts
@@ -5,7 +5,8 @@ import queryClient from "@/services/QueryClient"
import { BOOKING_AVAILABLITY_KEY } from "@/services/Booking/BookingQueries"
import {
ALL_BOOKINGS_BETWEEN_RANGE_QUERY,
- ALL_USERS_QUERY
+ ALL_USERS_QUERY,
+ BOOKING_HISTORY_QUERY
} from "./AdminQueries"
import { CombinedUserData } from "@/models/User"
import { replaceUserInPage } from "./AdminUtils"
@@ -93,6 +94,10 @@ export function useMakeDatesAvailableMutation(
retry: false,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [BOOKING_AVAILABLITY_KEY] })
+
+ queryClient.invalidateQueries({
+ queryKey: [BOOKING_HISTORY_QUERY]
+ })
}
})
}
@@ -116,6 +121,10 @@ export function useMakeDatesUnavailableMutation(
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [BOOKING_AVAILABLITY_KEY] })
+
+ queryClient.invalidateQueries({
+ queryKey: [BOOKING_HISTORY_QUERY]
+ })
}
})
}
@@ -130,6 +139,10 @@ export function useAddUserToBookingMutation() {
queryKey: [ALL_BOOKINGS_BETWEEN_RANGE_QUERY]
})
+ queryClient.invalidateQueries({
+ queryKey: [BOOKING_HISTORY_QUERY]
+ })
+
queryClient.invalidateQueries({
queryKey: [BOOKING_AVAILABLITY_KEY]
})
@@ -147,6 +160,10 @@ export function useDeleteBookingMutation() {
queryKey: [ALL_BOOKINGS_BETWEEN_RANGE_QUERY]
})
+ queryClient.invalidateQueries({
+ queryKey: [BOOKING_HISTORY_QUERY]
+ })
+
queryClient.invalidateQueries({
queryKey: [BOOKING_AVAILABLITY_KEY]
})
diff --git a/client/src/services/Admin/AdminQueries.ts b/client/src/services/Admin/AdminQueries.ts
index 4882f6499..143c8f229 100644
--- a/client/src/services/Admin/AdminQueries.ts
+++ b/client/src/services/Admin/AdminQueries.ts
@@ -4,6 +4,7 @@ import AdminService from "./AdminService"
export const ALL_USERS_QUERY = "allUsers"
export const ALL_BOOKINGS_BETWEEN_RANGE_QUERY = "bookings-between-range"
+export const BOOKING_HISTORY_QUERY = "latest-booking-history"
export function useUsersQuery() {
return useInfiniteQuery({
@@ -30,3 +31,13 @@ export function useAdminBookingsQuery(
staleTime: 30000 // 30 sec
})
}
+
+export function useBookingHistoryQuery() {
+ return useInfiniteQuery({
+ queryKey: [BOOKING_HISTORY_QUERY],
+ queryFn: AdminService.getBookingHistory,
+ retry: 0,
+ initialPageParam: undefined,
+ getNextPageParam: (lastPage) => lastPage.nextCursor
+ })
+}
diff --git a/client/src/services/Admin/AdminService.ts b/client/src/services/Admin/AdminService.ts
index 24bb3935b..2f4c41525 100644
--- a/client/src/services/Admin/AdminService.ts
+++ b/client/src/services/Admin/AdminService.ts
@@ -167,6 +167,31 @@ const AdminService = {
}
return data?.data
+ },
+ getBookingHistory: async function ({
+ pageParam,
+ limit = 200
+ }: {
+ pageParam?: string
+ limit?: number
+ }) {
+ const { response, data } = await fetchClient.GET(
+ "/admin/bookings/history",
+ {
+ params: {
+ query: {
+ limit,
+ cursor: pageParam
+ }
+ }
+ }
+ )
+
+ if (!response.ok || !data) {
+ throw new Error(`Failed to fetch ${limit} of the latest booking items`)
+ }
+
+ return data
}
} as const
diff --git a/server/src/middleware/__generated__/routes.ts b/server/src/middleware/__generated__/routes.ts
index de9ba4320..049aee8eb 100644
--- a/server/src/middleware/__generated__/routes.ts
+++ b/server/src/middleware/__generated__/routes.ts
@@ -485,15 +485,6 @@ const models: TsoaRoute.Models = {
"additionalProperties": false,
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
- "FetchLatestBookingEventRequest": {
- "dataType": "refObject",
- "properties": {
- "limit": {"dataType":"double","required":true,"validators":{"maximum":{"errorMsg":"please select a smaller limit (max 500)","value":500},"minimum":{"errorMsg":"please choose a positive, non-zero limit","value":1}}},
- "cursor": {"dataType":"string"},
- },
- "additionalProperties": false,
- },
- // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
};
const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true});
@@ -1247,14 +1238,15 @@ export function RegisterRoutes(app: Router) {
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
- app.post('/admin/bookings/history',
+ app.get('/admin/bookings/history',
authenticateMiddleware([{"jwt":["admin"]}]),
...(fetchMiddlewares(AdminController)),
...(fetchMiddlewares(AdminController.prototype.getLatestHistory)),
function AdminController_getLatestHistory(request: ExRequest, response: ExResponse, next: any) {
const args: Record = {
- requestBody: {"in":"body","name":"requestBody","required":true,"ref":"FetchLatestBookingEventRequest"},
+ limit: {"in":"query","name":"limit","required":true,"dataType":"double"},
+ cursor: {"in":"query","name":"cursor","dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
diff --git a/server/src/middleware/__generated__/swagger.json b/server/src/middleware/__generated__/swagger.json
index 4371cadab..61a737b8c 100644
--- a/server/src/middleware/__generated__/swagger.json
+++ b/server/src/middleware/__generated__/swagger.json
@@ -1201,25 +1201,6 @@
},
"type": "object",
"additionalProperties": false
- },
- "FetchLatestBookingEventRequest": {
- "properties": {
- "limit": {
- "type": "number",
- "format": "double",
- "maximum": 500,
- "minimum": 1
- },
- "cursor": {
- "type": "string",
- "description": "The id of the cursor to continue paginating from"
- }
- },
- "required": [
- "limit"
- ],
- "type": "object",
- "additionalProperties": false
}
},
"securitySchemes": {
@@ -2099,7 +2080,7 @@
}
},
"/admin/bookings/history": {
- "post": {
+ "get": {
"operationId": "GetLatestHistory",
"responses": {
"200": {
@@ -2121,19 +2102,25 @@
]
}
],
- "parameters": [],
- "requestBody": {
- "description": "- contains the pagination variables",
- "required": true,
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/FetchLatestBookingEventRequest",
- "description": "- contains the pagination variables"
- }
+ "parameters": [
+ {
+ "in": "query",
+ "name": "limit",
+ "required": true,
+ "schema": {
+ "format": "double",
+ "type": "number"
+ }
+ },
+ {
+ "in": "query",
+ "name": "cursor",
+ "required": false,
+ "schema": {
+ "type": "string"
}
}
- }
+ ]
}
}
},
diff --git a/server/src/middleware/tests/AdminController.test.ts b/server/src/middleware/tests/AdminController.test.ts
index 9def29548..829e76c3c 100644
--- a/server/src/middleware/tests/AdminController.test.ts
+++ b/server/src/middleware/tests/AdminController.test.ts
@@ -741,18 +741,19 @@ describe("AdminController endpoint tests", () => {
describe("/admin/bookings/history", () => {
it("should be scoped to admins only", async () => {
let res = await request
- .post("/admin/bookings/history")
+ .get(`/admin/bookings/history?limit=100`)
.set("Authorization", `Bearer ${memberToken}`)
- .send({ limit: 1 })
+ .send({})
expect(res.status).toEqual(401)
res = await request
- .post("/admin/bookings/history")
+ .get(`/admin/bookings/history?limit=100`)
.set("Authorization", `Bearer ${guestToken}`)
- .send({ limit: 1 })
+ .send({})
expect(res.status).toEqual(401)
- res = await request.post("/admin/bookings/history").send({ limit: 1 })
+ res = await request.get(`/admin/bookings/history?limit=100`).send({})
+
expect(res.status).toEqual(401)
})
@@ -779,9 +780,9 @@ describe("AdminController endpoint tests", () => {
})
let res = await request
- .post("/admin/bookings/history")
+ .get(`/admin/bookings/history?limit=1`)
.set("Authorization", `Bearer ${adminToken}`)
- .send({ limit: 1 })
+ .send({})
expect(res.status).toEqual(200)
expect(res.body.historyEvents).toHaveLength(1)
@@ -790,17 +791,17 @@ describe("AdminController endpoint tests", () => {
* Pagination Test
*/
res = await request
- .post("/admin/bookings/history")
+ .get(`/admin/bookings/history?limit=100&cursor=${res.body.nextCursor}`)
.set("Authorization", `Bearer ${adminToken}`)
- .send({ limit: 100, cursor: res.body.nextCursor })
+ .send({})
expect(res.status).toEqual(200)
expect(res.body.historyEvents).toHaveLength(1)
res = await request
- .post("/admin/bookings/history")
+ .get(`/admin/bookings/history?limit=2`)
.set("Authorization", `Bearer ${adminToken}`)
- .send({ limit: 2 })
+ .send({})
expect(res.body.historyEvents).toHaveLength(2)
})
diff --git a/server/src/service-layer/controllers/AdminController.ts b/server/src/service-layer/controllers/AdminController.ts
index 6b8f0588d..045aee21f 100644
--- a/server/src/service-layer/controllers/AdminController.ts
+++ b/server/src/service-layer/controllers/AdminController.ts
@@ -161,7 +161,7 @@ export class AdminController extends Controller {
// Was available
if (bookingSlotForDate.max_bookings > EMPTY_BOOKING_SLOTS) {
// TODO: change to proper functionality (i.e not completely make it empty)
- change = bookingSlotForDate.max_bookings - EMPTY_BOOKING_SLOTS
+ change = EMPTY_BOOKING_SLOTS - bookingSlotForDate.max_bookings
await bookingSlotService.updateBookingSlot(bookingSlotForDate.id, {
max_bookings: EMPTY_BOOKING_SLOTS
})
@@ -660,12 +660,11 @@ export class AdminController extends Controller {
* @returns the list of latest history events
*/
@SuccessResponse("200", "History Events Fetched")
- @Post("bookings/history")
+ @Get("bookings/history")
public async getLatestHistory(
- @Body() requestBody: FetchLatestBookingEventRequest
+ @Query() limit: FetchLatestBookingEventRequest["limit"],
+ @Query() cursor?: FetchLatestBookingEventRequest["cursor"]
): Promise {
- const { limit, cursor } = requestBody
-
try {
const bookingHistoryService = new BookingHistoryService()