diff --git a/components/DuplicateEventModal.tsx b/components/DuplicateEventModal.tsx new file mode 100644 index 0000000..ef78854 --- /dev/null +++ b/components/DuplicateEventModal.tsx @@ -0,0 +1,137 @@ +import React, { useEffect } from "react" +import { useState } from "react" +import { Button, Modal } from "flowbite-react" +import { atom, useRecoilState } from "recoil" +import useEvent from "lib/hooks/useEvent" +import useEvents from "lib/hooks/useEvents" +import { postEvent } from "lib/actions/postEvent" +import { HiCheckCircle } from "react-icons/hi" +import { useForm } from "react-hook-form" +import { putEvent } from "lib/actions/putEvent" +import DateTimeField from "./forms/DateTimeField" +import { Stack } from "@mui/material" +import { EventItem } from "pages/api/eventGroup/[eventGroupId]" +import { Toast } from "flowbite-react" + +interface DuplicateEventProps { + onClose: () => void +} + +interface DuplicateEventForm { + date: Date +} + +export const duplicateEventModalState = atom({ + key: "duplicateEventModalState", + default: false, +}) + +export const duplicateEventIdState = atom({ + key: "duplicateEventIdState", + default: undefined, +}) + +export const DuplicateEventModal: React.FC = ({ onClose }) => { + const [showDuplicateEventModal, setShowDuplicateEventModal] = useRecoilState(duplicateEventModalState) + const [duplicateEventId, setDuplicateEventId] = useRecoilState(duplicateEventIdState) + const { events: currentEvents, mutate } = useEvents() + const { event } = useEvent(duplicateEventId) + const { events, mutate: mutateEvents } = useEvents() + const [success, setSuccess] = useState(null) + const { control, handleSubmit, reset } = useForm({ defaultValues: { date: event?.start } }) + + useEffect(() => { + reset({ date: event?.start }) + }, [event]) + + const duplicateEventGroup = (eg: any, dateOffset: number) => { + let newEg = JSON.parse(JSON.stringify(eg)) + newEg.id = undefined + newEg.EventItem = [] + newEg.start = new Date(new Date(newEg.start).getTime() + dateOffset) + newEg.end = new Date(new Date(newEg.end).getTime() + dateOffset) + eg.EventItem.map((ei: EventItem) => { + const newEi = duplicateEventItem(ei) + newEg.EventItem.push(newEi) + }) + return newEg + } + + const duplicateEventItem = (ei: any) => { + let newEi = JSON.parse(JSON.stringify(ei)) + newEi.id = undefined + newEi.groupId = undefined + return newEi + } + + const duplicateEvent = (data: DuplicateEventForm) => { + // make a new event + if (!event) return + const newDate = data.date + const dateOffset = new Date(newDate).getTime() - new Date(event.start).getTime() + + let eventDuplicate = JSON.parse(JSON.stringify(event)) + // remove EG and UOE and id to prevent them being duplicated with duplicate ids + eventDuplicate.EventGroup = [] + eventDuplicate.UserOnEvent = [] + eventDuplicate.id = undefined + eventDuplicate.start = new Date(new Date(eventDuplicate.start).getTime() + dateOffset) + eventDuplicate.end = new Date(new Date(eventDuplicate.end).getTime() + dateOffset) + + postEvent(eventDuplicate).then((newEvent) => { + // here we take the newly saved event and add copies of the eventgroups and items + newEvent.EventGroup = [] + event.EventGroup.map((eg) => { + const newEg = duplicateEventGroup(eg, dateOffset) + newEvent.EventGroup.push(newEg) + }) + // prevent createmany from failing + newEvent.UserOnEvent = [] + + putEvent(newEvent).then((updatedEvent) => { + const finalEvent = updatedEvent.event + // we ignore the ts error because it doesn't seem to understand the input for mutate here + // @ts-ignore + mutateEvents([...events, finalEvent]) + setSuccess("success") + setTimeout(() => { + setShowDuplicateEventModal(false) + onClose() + setSuccess(null) + }, 1500) + }) + }) + } + + return ( + + Duplicate Event + + +

This will create a duplicate of event: {event?.name}

+

+ Enter a start date for the event. All dates and times will be appropriately adjusted relative to the start + date but you will likely need to further adjust the schedule to suit the specifics of the course. +

+ + + {success && ( + +
+ +
+
Event Successfully Duplicated!
+
+ )} +
+
+
+ ) +} diff --git a/components/EventsView.tsx b/components/EventsView.tsx index 313892b..fc4bb68 100644 --- a/components/EventsView.tsx +++ b/components/EventsView.tsx @@ -8,9 +8,12 @@ import useEvents from "lib/hooks/useEvents" import useProfile from "lib/hooks/useProfile" import useActiveEvent from "lib/hooks/useActiveEvents" import { postEvent } from "lib/actions/postEvent" -import { MdDelete } from "react-icons/md" +import { MdContentCopy, MdDelete } from "react-icons/md" import { useRecoilState } from "recoil" -import { deleteEventModalState, deleteEventIdState, DeleteEventModal } from "components/deleteEventModal" +import { deleteEventModalState, deleteEventIdState } from "components/deleteEventModal" +import { duplicateEventModalState, duplicateEventIdState } from "components/DuplicateEventModal" +import { Tooltip } from "@mui/material" +import Stack from "./ui/Stack" type EventsProps = { material: Material @@ -22,6 +25,8 @@ const EventsView: React.FC = ({ material, events }) => { const [showDateTime, setShowDateTime] = useState(false) const [showDeleteEventModal, setShowDeleteEventModal] = useRecoilState(deleteEventModalState) const [deleteEventId, setDeleteEventId] = useRecoilState(deleteEventIdState) + const [showDuplicateEventModal, setShowDuplicateEventModal] = useRecoilState(duplicateEventModalState) + const [duplicateEventId, setDuplicateEventId] = useRecoilState(duplicateEventIdState) useEffect(() => { setShowDateTime(true) @@ -58,6 +63,11 @@ const EventsView: React.FC = ({ material, events }) => { setDeleteEventId(eventId) } + const openDuplicateEventModal = (eventId: number) => { + setShowDuplicateEventModal(true) + setDuplicateEventId(eventId) + } + return ( {events.map((event) => { @@ -70,12 +80,24 @@ const EventsView: React.FC = ({ material, events }) => { {showDateTime && event.start.toLocaleString([], { dateStyle: "medium", timeStyle: "short" })} {isAdmin && ( - openDeleteEventModal(event.id)} - /> + + + openDuplicateEventModal(event.id)} + /> + + + openDeleteEventModal(event.id)} + /> + + )} diff --git a/components/Overlay.tsx b/components/Overlay.tsx index 8f1716c..457fea9 100644 --- a/components/Overlay.tsx +++ b/components/Overlay.tsx @@ -9,7 +9,7 @@ import Sidebar from "./Sidebar" import { SearchDialog, searchQueryState } from "components/SearchDialog" import { useRecoilState } from "recoil" import { DeleteEventModal, deleteEventModalState } from "components/deleteEventModal" -import { enableSearch } from "lib/search/enableSearch" +import { DuplicateEventModal, duplicateEventModalState } from "components/DuplicateEventModal" interface Props { material: Material @@ -41,6 +41,7 @@ const Overlay: NextPage = ({ const [showSearch, setShowSearch] = useRecoilState(searchQueryState) const [showTopButtons, setShowTopButtons] = useState(false) const [showDeleteEventModal, setShowDeleteEventModal] = useRecoilState(deleteEventModalState) + const [showDuplicateEventModal, setShowDuplicateEventModal] = useRecoilState(duplicateEventModalState) useEffect(() => { const handleScroll = () => { @@ -64,6 +65,10 @@ const Overlay: NextPage = ({ setShowDeleteEventModal(false) } + const closeDuplicateEvent = () => { + setShowDuplicateEventModal(false) + } + const handleClose = () => { setSidebarOpen(false) } @@ -108,6 +113,7 @@ const Overlay: NextPage = ({ + diff --git a/components/forms/DateTimeField.tsx b/components/forms/DateTimeField.tsx index 574801a..be1cb67 100644 --- a/components/forms/DateTimeField.tsx +++ b/components/forms/DateTimeField.tsx @@ -12,9 +12,16 @@ type Props = { name: FieldPath control: Control rules?: Object + defaultValue?: Date } -function DateTimeField({ label, name, control, rules }: Props): React.ReactElement { +function DateTimeField({ + label, + name, + control, + rules, + defaultValue, +}: Props): React.ReactElement { const labelId = `${name}-label` return ( ({ label, name, control, rules }: P format="YYYY-MM-DD HH:mm" name={name} ampm={false} - className="font-normal bg-grey-100 dark:bg-gray-700 dark:text-gray-200" + className="font-normal bg-grey-100 dark:bg-gray-600 dark:text-gray-200" value={dayjs(value)} - slotProps={{ openPickerIcon: { className: "bg-grey-100 dark:bg-gray-700 dark:text-gray-200" } }} + slotProps={{ openPickerIcon: { className: "bg-grey-100 dark:bg-gray-600 dark:text-gray-200" } }} onChange={onChange} /> diff --git a/lib/actions/postEvent.ts b/lib/actions/postEvent.ts index d9eedf4..3510d0b 100644 --- a/lib/actions/postEvent.ts +++ b/lib/actions/postEvent.ts @@ -1,12 +1,13 @@ import { basePath } from "lib/basePath" -import { Event } from "lib/types" +import { Event } from "pages/api/event/[eventId]" // POST /api/events -export const postEvent = async (): Promise => { +export const postEvent = async (data?: Event): Promise => { const apiPath = `${basePath}/api/event` const requestOptions = { method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), } return fetch(apiPath, requestOptions) .then((response) => response.json()) diff --git a/pages/api/event.ts b/pages/api/event.ts index 7c388da..c9b6585 100644 --- a/pages/api/event.ts +++ b/pages/api/event.ts @@ -31,8 +31,16 @@ const Events = async (req: NextApiRequest, res: NextApiResponse) => { res.status(403).json({ error: "Forbidden" }) return } - const event = await prisma.event.create({ data: {} }) - res.status(201).json({ event: event }) + if (!req.body) { + const event = await prisma.event.create({ data: {} }) + res.status(201).json({ event: event }) + } else { + req.body.UserOnEvent = undefined + req.body.EventGroup = undefined + req.body.id = undefined + const event = await prisma.event.create({ data: req.body }) + res.status(201).json({ event: event }) + } } else if (req.method === "GET") { if (isAdmin) { const events: Event[] = await prisma.event.findMany() diff --git a/pages/api/event/[eventId].ts b/pages/api/event/[eventId].ts index 84bd41f..341960f 100644 --- a/pages/api/event/[eventId].ts +++ b/pages/api/event/[eventId].ts @@ -78,10 +78,6 @@ const eventHandler = async (req: NextApiRequest, res: NextApiResponse) => const eventGroupData: EventGroup[] = req.body.event.EventGroup const userOnEventData: UserOnEvent[] = req.body.event.UserOnEvent - console.log("eventGroupData", eventGroupData) - console.log("userOnEventData", userOnEventData) - console.log("req.body", req.body) - if (!isAdmin) { res.status(401).json({ error: "Unauthorized" }) return