data={data || [defaultData]}
- operationType="multiple-operations"
- rowOperations={rowOperations}
+ operationType="single-operation"
+ rowOperations={rowOperation}
// Make sure that this is smaller than the amount we fetch in the `AdminService` for better UX
showPerPage={32}
groupSameRows
diff --git a/client/src/components/composite/Admin/AdminBookingView/WrappedAdminBookingView.tsx b/client/src/components/composite/Admin/AdminBookingView/WrappedAdminBookingView.tsx
index 4bda0405e..c6defdec1 100644
--- a/client/src/components/composite/Admin/AdminBookingView/WrappedAdminBookingView.tsx
+++ b/client/src/components/composite/Admin/AdminBookingView/WrappedAdminBookingView.tsx
@@ -4,6 +4,8 @@ import { DateUtils } from "components/utils/DateUtils"
import { AdminBookingViewContext } from "./AdminBookingViewContext"
import { useContext, useMemo } from "react"
import { Timestamp } from "firebase/firestore"
+import { TableRowOperation } from "components/generic/ReusableTable/TableUtils"
+import { useDeleteBookingMutation } from "services/Admin/AdminMutations"
/**
* Should be wrapped the `AdminBookingViewProvider`
@@ -14,7 +16,7 @@ const WrappedAdminBookingView = () => {
handleSelectedDateChange
} = useContext(AdminBookingViewContext)
- const { data, isLoading } = useAdminBookingsQuery(
+ const { data, isLoading: isFetchingUsers } = useAdminBookingsQuery(
Timestamp.fromDate(DateUtils.convertLocalDateToUTCDate(startDate)),
Timestamp.fromDate(DateUtils.convertLocalDateToUTCDate(endDate))
)
@@ -24,7 +26,7 @@ const WrappedAdminBookingView = () => {
const newData: BookingMemberColumnFormat = {
uid: ""
}
- newData.uid = user.uid
+ newData.uid = user.bookingId
newData.Date = DateUtils.formattedNzDate(
new Date(DateUtils.timestampMilliseconds(date.date))
)
@@ -44,10 +46,34 @@ const WrappedAdminBookingView = () => {
),
[dataList]
)
+
+ const { mutateAsync: deleteBooking, isPending: isDeletingBooking } =
+ useDeleteBookingMutation()
+ const rowOperations: [TableRowOperation] = [
+ {
+ name: "delete booking",
+ handler: (bookingId: string) => {
+ const matchingBooking = sortedData?.find(
+ (data) => data.uid === bookingId
+ )
+ if (
+ confirm(
+ `Are you SURE you want to delete the booking for the user ${matchingBooking?.Email} on the date ${matchingBooking?.Date}?
+ This can NOT be undone!
+ `
+ )
+ ) {
+ deleteBooking(bookingId)
+ }
+ }
+ }
+ ]
+
return (
diff --git a/client/src/components/generic/ReusableTable/Table.tsx b/client/src/components/generic/ReusableTable/Table.tsx
index 37f3b6d8c..66f33540b 100644
--- a/client/src/components/generic/ReusableTable/Table.tsx
+++ b/client/src/components/generic/ReusableTable/Table.tsx
@@ -113,6 +113,7 @@ export const OperationButton = <
rowOperations && rowOperations[0]?.handler(uid)}
>
X
diff --git a/client/src/models/__generated__/schema.d.ts b/client/src/models/__generated__/schema.d.ts
index 7d3723f2f..6d3deaf57 100644
--- a/client/src/models/__generated__/schema.d.ts
+++ b/client/src/models/__generated__/schema.d.ts
@@ -250,7 +250,7 @@ export interface components {
};
/** @enum {string} */
UserAccountTypes: "admin" | "member" | "guest";
- CombinedUserData: {
+ BookingIdandUserData: {
date_of_birth: components["schemas"]["FirebaseFirestore.Timestamp"];
does_snowboarding?: boolean;
does_racing?: boolean;
@@ -278,11 +278,12 @@ export interface components {
email: string;
/** @description What type of account the user has */
membership: components["schemas"]["UserAccountTypes"];
+ bookingId: string;
};
/** @description Represents the response structure for fetching users by date range. */
UsersByDateRangeResponse: {
data?: {
- users: components["schemas"]["CombinedUserData"][];
+ users: components["schemas"]["BookingIdandUserData"][];
date: components["schemas"]["FirebaseFirestore.Timestamp"];
}[];
error?: string;
@@ -327,6 +328,35 @@ export interface components {
DeleteBookingRequest: {
bookingID: string;
};
+ CombinedUserData: {
+ date_of_birth: components["schemas"]["FirebaseFirestore.Timestamp"];
+ does_snowboarding?: boolean;
+ does_racing?: boolean;
+ does_ski?: boolean;
+ /** Format: double */
+ phone_number: number;
+ gender?: string;
+ emergency_contact?: string;
+ first_name: string;
+ last_name: string;
+ dietary_requirements: string;
+ /** @description **OPTIONAL** field that the user should have the choice to provide */
+ ethnicity?: string;
+ faculty?: string;
+ university?: string;
+ student_id?: string;
+ university_year?: string;
+ /** @description For identification DO NOT RETURN to users in exposed endpoints */
+ stripe_id?: string;
+ /** @description Firebase identifier of the user *data* based on the firestore document */
+ uid: string;
+ /** @description Formatted UTC date string of when the account was created */
+ dateJoined?: string;
+ /** @description The email the user uses to log in */
+ email: string;
+ /** @description What type of account the user has */
+ membership: components["schemas"]["UserAccountTypes"];
+ };
AllUsersResponse: {
error?: string;
message?: string;
diff --git a/client/src/services/Admin/AdminMutations.ts b/client/src/services/Admin/AdminMutations.ts
index 856981a09..66fa22a11 100644
--- a/client/src/services/Admin/AdminMutations.ts
+++ b/client/src/services/Admin/AdminMutations.ts
@@ -132,3 +132,16 @@ export function useAddUserToBookingMutation() {
}
})
}
+
+export function useDeleteBookingMutation() {
+ return useMutation({
+ mutationKey: ["delete-booking"],
+ retry: false,
+ mutationFn: AdminService.deleteBooking,
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: [ALL_BOOKINGS_BETWEEN_RANGE_QUERY]
+ })
+ }
+ })
+}
diff --git a/client/src/services/Admin/AdminService.ts b/client/src/services/Admin/AdminService.ts
index bae3d746b..b40362a65 100644
--- a/client/src/services/Admin/AdminService.ts
+++ b/client/src/services/Admin/AdminService.ts
@@ -50,7 +50,16 @@ const AdminService = {
})
if (!response.ok) throw new Error(`Failed to promote ${uid}`)
},
-
+ deleteBooking: async function (id: string) {
+ const { response } = await fetchClient.POST("/admin/bookings/delete", {
+ body: {
+ bookingID: id
+ }
+ })
+ if (!response.ok) {
+ throw new Error(`Failed to delete booking with id ${id}`)
+ }
+ },
getBookingsBetweenDateRange: async function ({
startDate = Timestamp.fromDate(new Date(Date.now())),
endDate = Timestamp.fromDate(new Date(Date.now()))
diff --git a/server/src/middleware/__generated__/routes.ts b/server/src/middleware/__generated__/routes.ts
index 1e45fcef1..345d3449a 100644
--- a/server/src/middleware/__generated__/routes.ts
+++ b/server/src/middleware/__generated__/routes.ts
@@ -221,7 +221,7 @@ const models: TsoaRoute.Models = {
"enums": ["admin","member","guest"],
},
// 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
- "CombinedUserData": {
+ "BookingIdandUserData": {
"dataType": "refObject",
"properties": {
"date_of_birth": {"ref":"FirebaseFirestore.Timestamp","required":true},
@@ -244,6 +244,7 @@ const models: TsoaRoute.Models = {
"dateJoined": {"dataType":"string"},
"email": {"dataType":"string","required":true},
"membership": {"ref":"UserAccountTypes","required":true},
+ "bookingId": {"dataType":"string","required":true},
},
"additionalProperties": false,
},
@@ -251,7 +252,7 @@ const models: TsoaRoute.Models = {
"UsersByDateRangeResponse": {
"dataType": "refObject",
"properties": {
- "data": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"users":{"dataType":"array","array":{"dataType":"refObject","ref":"CombinedUserData"},"required":true},"date":{"ref":"FirebaseFirestore.Timestamp","required":true}}}},
+ "data": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"users":{"dataType":"array","array":{"dataType":"refObject","ref":"BookingIdandUserData"},"required":true},"date":{"ref":"FirebaseFirestore.Timestamp","required":true}}}},
"error": {"dataType":"string"},
},
"additionalProperties": false,
@@ -314,6 +315,33 @@ 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
+ "CombinedUserData": {
+ "dataType": "refObject",
+ "properties": {
+ "date_of_birth": {"ref":"FirebaseFirestore.Timestamp","required":true},
+ "does_snowboarding": {"dataType":"boolean"},
+ "does_racing": {"dataType":"boolean"},
+ "does_ski": {"dataType":"boolean"},
+ "phone_number": {"dataType":"double","required":true},
+ "gender": {"dataType":"string"},
+ "emergency_contact": {"dataType":"string","validators":{"isString":{"errorMsg":"Please enter a name"}}},
+ "first_name": {"dataType":"string","required":true,"validators":{"isString":{"errorMsg":"Please enter your First Name"}}},
+ "last_name": {"dataType":"string","required":true,"validators":{"isString":{"errorMsg":"Please enter your Second Name"}}},
+ "dietary_requirements": {"dataType":"string","required":true,"validators":{"isString":{"errorMsg":"Please write your dietary requirements"}}},
+ "ethnicity": {"dataType":"string"},
+ "faculty": {"dataType":"string","validators":{"isString":{"errorMsg":"Please enter your faculty"}}},
+ "university": {"dataType":"string","validators":{"isString":{"errorMsg":"Please enter your university"}}},
+ "student_id": {"dataType":"string","validators":{"isString":{"errorMsg":"Please enter your student ID"}}},
+ "university_year": {"dataType":"string","validators":{"isString":{"errorMsg":"Please enter your year of study"}}},
+ "stripe_id": {"dataType":"string"},
+ "uid": {"dataType":"string","required":true},
+ "dateJoined": {"dataType":"string"},
+ "email": {"dataType":"string","required":true},
+ "membership": {"ref":"UserAccountTypes","required":true},
+ },
+ "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
"AllUsersResponse": {
"dataType": "refObject",
"properties": {
diff --git a/server/src/middleware/__generated__/swagger.json b/server/src/middleware/__generated__/swagger.json
index 527c1b84c..d814e5802 100644
--- a/server/src/middleware/__generated__/swagger.json
+++ b/server/src/middleware/__generated__/swagger.json
@@ -487,7 +487,7 @@
],
"type": "string"
},
- "CombinedUserData": {
+ "BookingIdandUserData": {
"properties": {
"date_of_birth": {
"$ref": "#/components/schemas/FirebaseFirestore.Timestamp"
@@ -555,6 +555,9 @@
"membership": {
"$ref": "#/components/schemas/UserAccountTypes",
"description": "What type of account the user has"
+ },
+ "bookingId": {
+ "type": "string"
}
},
"required": [
@@ -565,7 +568,8 @@
"dietary_requirements",
"uid",
"email",
- "membership"
+ "membership",
+ "bookingId"
],
"type": "object",
"additionalProperties": false
@@ -578,7 +582,7 @@
"properties": {
"users": {
"items": {
- "$ref": "#/components/schemas/CombinedUserData"
+ "$ref": "#/components/schemas/BookingIdandUserData"
},
"type": "array"
},
@@ -723,6 +727,89 @@
"type": "object",
"additionalProperties": false
},
+ "CombinedUserData": {
+ "properties": {
+ "date_of_birth": {
+ "$ref": "#/components/schemas/FirebaseFirestore.Timestamp"
+ },
+ "does_snowboarding": {
+ "type": "boolean"
+ },
+ "does_racing": {
+ "type": "boolean"
+ },
+ "does_ski": {
+ "type": "boolean"
+ },
+ "phone_number": {
+ "type": "number",
+ "format": "double"
+ },
+ "gender": {
+ "type": "string"
+ },
+ "emergency_contact": {
+ "type": "string"
+ },
+ "first_name": {
+ "type": "string"
+ },
+ "last_name": {
+ "type": "string"
+ },
+ "dietary_requirements": {
+ "type": "string"
+ },
+ "ethnicity": {
+ "type": "string",
+ "description": "**OPTIONAL** field that the user should have the choice to provide"
+ },
+ "faculty": {
+ "type": "string"
+ },
+ "university": {
+ "type": "string"
+ },
+ "student_id": {
+ "type": "string"
+ },
+ "university_year": {
+ "type": "string"
+ },
+ "stripe_id": {
+ "type": "string",
+ "description": "For identification DO NOT RETURN to users in exposed endpoints"
+ },
+ "uid": {
+ "type": "string",
+ "description": "Firebase identifier of the user *data* based on the firestore document"
+ },
+ "dateJoined": {
+ "type": "string",
+ "description": "Formatted UTC date string of when the account was created"
+ },
+ "email": {
+ "type": "string",
+ "description": "The email the user uses to log in"
+ },
+ "membership": {
+ "$ref": "#/components/schemas/UserAccountTypes",
+ "description": "What type of account the user has"
+ }
+ },
+ "required": [
+ "date_of_birth",
+ "phone_number",
+ "first_name",
+ "last_name",
+ "dietary_requirements",
+ "uid",
+ "email",
+ "membership"
+ ],
+ "type": "object",
+ "additionalProperties": false
+ },
"AllUsersResponse": {
"properties": {
"error": {
diff --git a/server/src/service-layer/controllers/AdminController.ts b/server/src/service-layer/controllers/AdminController.ts
index b7a3fbb7d..3be210be4 100644
--- a/server/src/service-layer/controllers/AdminController.ts
+++ b/server/src/service-layer/controllers/AdminController.ts
@@ -149,6 +149,7 @@ export class AdminController extends Controller {
}
@SuccessResponse("200", "Booking deleted successfuly")
+ // TODO: Refactor this to be a DELETE request
@Post("/bookings/delete")
public async removeBooking(
@Body() requestBody: DeleteBookingRequest
diff --git a/server/src/service-layer/response-models/BookingResponse.ts b/server/src/service-layer/response-models/BookingResponse.ts
index 5c28b1c85..7cd34df45 100644
--- a/server/src/service-layer/response-models/BookingResponse.ts
+++ b/server/src/service-layer/response-models/BookingResponse.ts
@@ -1,6 +1,6 @@
import { Timestamp } from "firebase-admin/firestore"
import { CommonResponse } from "./CommonResponse"
-import { CombinedUserData } from "./UserResponse"
+import { BookingIdandUserData } from "./UserResponse"
export interface BookingSlotUpdateResponse extends CommonResponse {
updatedBookingSlots?: { date: Timestamp; bookingSlotId: string }[]
@@ -14,7 +14,7 @@ export interface AllUserBookingSlotsResponse extends CommonResponse {
* Represents the response structure for fetching users by date range.
*/
export interface UsersByDateRangeResponse {
- data?: Array<{ date: Timestamp; users: CombinedUserData[] }>
+ data?: Array<{ date: Timestamp; users: BookingIdandUserData[] }>
error?: string
}