Skip to content

Commit

Permalink
Allow supervisors to cancel events (#245)
Browse files Browse the repository at this point in the history
* Added Tanstack Mutation for canceling events and modal

* In EventCardNew.tsx we added functionality to display if an event is canceled in red text. Also added a event status column to past events in ViewEvents.tsx

* feat: Add functionality to display canceled events and event status column

* Add check for past events when disabling buttons

* Format

* FIX: Cancel event button is grayed out even if event is upcoming and is not canceled.

* Add visual alert to EventForm

* Remove disablePastEvent functionality

* Fix modal

* a

---------

Co-authored-by: Akinfolami Akin-Alamu <aoa9@cornell.edu>
Co-authored-by: Jason Zheng <jasonz4200@gmail.com>
  • Loading branch information
3 people authored Jun 8, 2024
1 parent b3cd1c2 commit 9d442dd
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 7 deletions.
6 changes: 5 additions & 1 deletion frontend/src/components/atoms/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React, { ReactNode, forwardRef, Ref } from "react";
import MuiAlert from "@mui/material/Alert";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import CancelIcon from "@mui/icons-material/Cancel";
import ErrorIcon from "@mui/icons-material/Error";

interface AlertProps {
children: ReactNode;
variety: "success" | "error";
variety: "success" | "error" | "warning";
onClose: () => void;
[key: string]: any;
}
Expand All @@ -28,6 +29,9 @@ const Alert = forwardRef(
bgcolor = "error.light";
icon = <CancelIcon color="error" fontSize="inherit" />;
break;
case "warning":
bgcolor = "warning.light";
icon = <ErrorIcon color="warning" fontSize="inherit" />;
}

return (
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/atoms/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import CircularProgress from "@mui/material/CircularProgress";
interface ButtonProps {
children: ReactNode;
icon?: ReactNode;
variety?: "primary" | "secondary" | "tertiary" | "error";
variety?: "primary" | "secondary" | "tertiary" | "error" | "mainError";
size?: "small" | "medium";
loading?: boolean;
[key: string]: any;
Expand Down Expand Up @@ -53,6 +53,9 @@ const Button = ({
variant = "outlined";
color = "error";
break;
case "mainError":
variant = "contained";
color = "error";
}

return (
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/organisms/EventCardNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ViewEventsEvent } from "@/utils/types";
import { format } from "date-fns";
import Link from "next/link";
import { displayDateInfo } from "@/utils/helpers";
import Chip from "../atoms/Chip";

interface EventCardNewProps {
event: ViewEventsEvent;
Expand All @@ -18,7 +19,12 @@ const EventCardContent = ({ event }: EventCardNewProps) => {
const formattedEndTime = format(new Date(event.endDate), "hh:mm a");
const timeRange = `${formattedStartTime} - ${formattedEndTime}`;
const date = new Date(event.startDate);
const dateInfo = displayDateInfo(date);
const dateInfo =
event.status === "CANCELED" ? (
<Chip size="small" label="Canceled" color="error" />
) : (
displayDateInfo(date)
);
const url =
event.role === "Supervisor"
? `/events/${event.id}/attendees`
Expand All @@ -29,7 +35,11 @@ const EventCardContent = ({ event }: EventCardNewProps) => {
return (
<div>
<div className="flex flex-row gap-4">
<div className="font-semibold text-orange-500">
<div
className={`font-semibold ${
event.status === "CANCELED" ? "text-red-600" : "text-orange-500"
}`}
>
<IconText icon={<FiberManualRecordIcon className="text-xs" />}>
{dateInfo}
</IconText>
Expand Down
89 changes: 86 additions & 3 deletions frontend/src/components/organisms/EventForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { api } from "@/utils/api";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import Dropzone from "../atoms/Dropzone";
import EditorComp from "@/components/atoms/Editor";
import Modal from "../molecules/Modal";
import Alert from "../atoms/Alert";

interface EventFormProps {
eventId?: string | string[] | undefined;
Expand All @@ -45,6 +47,7 @@ type FormValues = {
startTime: Date;
endTime: Date;
mode: string;
status: string;
};

/** An EventForm page */
Expand Down Expand Up @@ -86,6 +89,10 @@ const EventForm = ({
setStatus(status);
};

type modalBodyProps = {
handleClose: () => void;
};

/** React hook form */
const {
register,
Expand Down Expand Up @@ -116,6 +123,11 @@ const EventForm = ({
/** Handles form errors for time and date validation */
const [errorMessage, setErrorMessage] = useState<string | null>(null);

/** Handles for cancelling an event */
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);

/** Tanstack mutation for creating a new event */
const {
mutateAsync: handleCreateNewEvent,
Expand Down Expand Up @@ -210,6 +222,25 @@ const EventForm = ({
},
});

/** Tanstack mutation for canceling an event */
const { mutateAsync: handleCancelEventAsync, isPending: cancelEventPending } =
useMutation({
mutationFn: async () => {
const { response } = await api.patch(`/events/${eventId}/status`, {
status: "CANCELED",
});
return response;
},
retry: false,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["event", eventId],
});
localStorage.setItem("eventCanceled", "true");
router.push("/events/view");
},
});

/** Helper for handling creating events */
const handleCreateEvent: SubmitHandler<FormValues> = async (data) => {
try {
Expand All @@ -230,8 +261,49 @@ const EventForm = ({
}
};

/** Helper for handling canceling events */
const handleCancelEvent = async () => {
try {
await handleCancelEventAsync();
} catch (error) {
setErrorNotificationOpen(true);
setErrorMessage("We were unable to cancel this event. Please try again");
}
};

/** Confirmation modal for canceling an event */
const ModalBody = ({ handleClose }: modalBodyProps) => {
return (
<div>
<p className="mt-0 text-center text-2xl font-semibold">
Are you sure you want to cancel this event?
</p>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="order-1 sm:order-2">
<Button variety="mainError" onClick={handleCancelEvent}>
Yes, cancel
</Button>
</div>
<div className="order-2 sm:order-1">
<Button variety="secondary" onClick={handleClose}>
Go back
</Button>
</div>
</div>
</div>
);
};

// Check if this event has been canceled
const thisEventHasBeenCanceled = eventDetails?.status === "CANCELED";

return (
<>
<Modal
open={open}
handleClose={handleClose}
children={<ModalBody handleClose={handleClose} />}
/>
{/* Error component */}
<Snackbar
variety="error"
Expand All @@ -240,6 +312,11 @@ const EventForm = ({
>
Error: {errorMessage}
</Snackbar>
{thisEventHasBeenCanceled && (
<div className="pb-6">
<Alert variety="warning">This event has been canceled.</Alert>
</div>
)}

<form
onSubmit={
Expand Down Expand Up @@ -427,15 +504,21 @@ const EventForm = ({
<Button variety="secondary">Go back</Button>
</Link>
</div>
{/* TODO: Add functionality */}
<div className="sm:col-start-7 sm:col-span-3">
<Button variety="error">Cancel event</Button>
<Button
variety="error"
loading={cancelEventPending}
disabled={editEventPending || thisEventHasBeenCanceled}
onClick={handleOpen}
>
Cancel event
</Button>
</div>
<div className="order-first sm:order-last sm:col-start-10 sm:col-span-3">
<Button
type="submit"
loading={editEventPending}
disabled={editEventPending}
disabled={editEventPending || thisEventHasBeenCanceled}
>
Save changes
</Button>
Expand Down
45 changes: 45 additions & 0 deletions frontend/src/components/organisms/ViewEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const UpcomingEvents = () => {
endDate: event["endDate"],
role: "Supervisor",
hours: eventHours(event["startDate"], event["endDate"]),
status: event["status"],
imageURL: event["imageURL"],
};
}
Expand All @@ -79,6 +80,7 @@ const UpcomingEvents = () => {
role: "Volunteer",
hours: eventHours(event["startDate"], event["endDate"]),
imageURL: event["imageURL"],
status: event["status"],
};
}
) || [];
Expand Down Expand Up @@ -213,6 +215,7 @@ const PastEvents = () => {
endDate: event["endDate"],
role: "Supervisor",
hours: eventHours(event["endDate"], event["startDate"]),
status: event["status"],
});
});

Expand All @@ -225,6 +228,7 @@ const PastEvents = () => {
endDate: event["endDate"],
role: "Volunteer",
hours: eventHours(event["endDate"], event["startDate"]),
status: event["status"],
});
});

Expand Down Expand Up @@ -311,6 +315,19 @@ const PastEvents = () => {
renderHeader: (params) => (
<div style={{ fontWeight: "bold" }}>{params.colDef.headerName}</div>
),
renderCell: (params) => (
<div className="flex items-center">
{params.row.name}
{params.row.status == "CANCELED" && (
<Chip
size="small"
label="Canceled"
color="error"
className="ml-3"
/>
)}
</div>
),
},
{
field: "startDate",
Expand Down Expand Up @@ -373,6 +390,19 @@ const PastEvents = () => {
renderHeader: (params) => (
<div style={{ fontWeight: "bold" }}>{params.colDef.headerName}</div>
),
renderCell: (params) => (
<div className="flex items-center">
{params.row.name}
{params.row.status == "CANCELED" && (
<Chip
size="small"
label="Canceled"
color="error"
className="ml-3"
/>
)}
</div>
),
},
{
field: "startDate",
Expand Down Expand Up @@ -519,6 +549,7 @@ const ViewEvents = () => {

const [isEventCreated, setIsEventCreated] = useState(false);
const [isEventEdited, setIsEventEdited] = useState(false);
const [isEventCanceled, setIsEventCanceled] = useState(false);

useEffect(() => {
const isEventCreated = localStorage.getItem("eventCreated");
Expand All @@ -530,6 +561,10 @@ const ViewEvents = () => {
setIsEventEdited(true);
localStorage.removeItem("eventEdited");
}
if (localStorage.getItem("eventCanceled")) {
setIsEventCanceled(true);
localStorage.removeItem("eventCanceled");
}
}, []);

return (
Expand All @@ -551,6 +586,16 @@ const ViewEvents = () => {
>
Your event has been successfully updated!
</Snackbar>

{/* Event canceled success notification */}
<Snackbar
variety="success"
open={isEventCanceled}
onClose={() => setIsEventCanceled(false)}
>
Your event has been successfully canceled!
</Snackbar>

<TabContainer
tabs={tabs}
left={<div className="text-3xl font-semibold">My Events</div>}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/events/[eventid]/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type eventData = {
startTime: Date;
endTime: Date;
mode: string;
status: string;
};

/** An EditEvent page */
Expand Down Expand Up @@ -48,6 +49,7 @@ const EditEvent = () => {
startTime: data?.startDate,
endTime: data?.endDate,
mode: data?.mode,
status: data?.status,
};

/** Loading screen */
Expand Down
1 change: 1 addition & 0 deletions frontend/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type ViewEventsEvent = {
ownerId?: string;
description?: string;
capacity?: number;
status?: EventStatus;
imageURL?: string;
};

Expand Down

0 comments on commit 9d442dd

Please sign in to comment.