From 12cf37cf4aeacaec4126c59d96d9aa916b5a561f Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Mon, 22 Jan 2024 16:17:28 +0000 Subject: [PATCH 1/3] stash --- components/DuplicateEventModal.tsx | 59 ++++++++++++++++++++++++++++++ components/EventsView.tsx | 38 +++++++++++++++---- components/Overlay.tsx | 8 +++- lib/actions/postEvent.ts | 3 +- 4 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 components/DuplicateEventModal.tsx diff --git a/components/DuplicateEventModal.tsx b/components/DuplicateEventModal.tsx new file mode 100644 index 0000000..6f37e30 --- /dev/null +++ b/components/DuplicateEventModal.tsx @@ -0,0 +1,59 @@ +import React from "react" +import { useState, useEffect } from "react" +import { Button, Modal } from "flowbite-react" +import Stack from "./ui/Stack" +import { atom, useRecoilState } from "recoil" +import useEvent from "lib/hooks/useEvent" +import useEvents from "lib/hooks/useEvents" +import { postEvent } from "lib/actions/postEvent" +import { Event } from "lib/types" + +interface DuplicateEventProps { + onClose: () => void +} + +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 [failure, setFailure] = useState(null) + + const handleDuplicateEvent = () => { + // make a new event + if (!event) return + postEvent(event).then((newEvent) => { + console.log("newEvent", newEvent) + }) + } + + return ( + + Duplicate Event + +

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

+ +
+
+ ) +} 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/lib/actions/postEvent.ts b/lib/actions/postEvent.ts index d9eedf4..209f59e 100644 --- a/lib/actions/postEvent.ts +++ b/lib/actions/postEvent.ts @@ -2,11 +2,12 @@ import { basePath } from "lib/basePath" import { Event } from "lib/types" // 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" }, + data: JSON.stringify(data), } return fetch(apiPath, requestOptions) .then((response) => response.json()) From 94ecef2218dc489c93fc03f15150555bdb644018 Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Wed, 24 Jan 2024 15:08:23 +0000 Subject: [PATCH 2/3] add button to duplicate event --- components/DuplicateEventModal.tsx | 115 ++++++++++++++++++++++++----- components/forms/DateTimeField.tsx | 9 ++- lib/actions/postEvent.ts | 4 +- pages/api/event.ts | 12 ++- pages/api/event/[eventId].ts | 4 - 5 files changed, 117 insertions(+), 27 deletions(-) diff --git a/components/DuplicateEventModal.tsx b/components/DuplicateEventModal.tsx index 6f37e30..e9b4866 100644 --- a/components/DuplicateEventModal.tsx +++ b/components/DuplicateEventModal.tsx @@ -1,17 +1,26 @@ -import React from "react" -import { useState, useEffect } from "react" +import React, { useEffect } from "react" +import { useState } from "react" import { Button, Modal } from "flowbite-react" -import Stack from "./ui/Stack" import { atom, useRecoilState } from "recoil" import useEvent from "lib/hooks/useEvent" import useEvents from "lib/hooks/useEvents" import { postEvent } from "lib/actions/postEvent" -import { Event } from "lib/types" +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, @@ -29,13 +38,68 @@ export const DuplicateEventModal: React.FC = ({ onClose }) const { event } = useEvent(duplicateEventId) const { events, mutate: mutateEvents } = useEvents() const [success, setSuccess] = useState(null) - const [failure, setFailure] = 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 handleDuplicateEvent = () => { + 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 - postEvent(event).then((newEvent) => { - console.log("newEvent", newEvent) + 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) + }) }) } @@ -43,16 +107,31 @@ export const DuplicateEventModal: React.FC = ({ onClose }) Duplicate Event -

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

- + +

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/forms/DateTimeField.tsx b/components/forms/DateTimeField.tsx index 574801a..c0768fc 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 ( => { @@ -7,7 +7,7 @@ export const postEvent = async (data?: Event): Promise => { const requestOptions = { method: "POST", headers: { "Content-Type": "application/json" }, - data: JSON.stringify(data), + 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 From df29357f49740108a999c06a59d03b838926b77d Mon Sep 17 00:00:00 2001 From: Alasdair Wilson Date: Wed, 24 Jan 2024 15:12:46 +0000 Subject: [PATCH 3/3] change styling of duplicate modal --- components/DuplicateEventModal.tsx | 3 +-- components/forms/DateTimeField.tsx | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/components/DuplicateEventModal.tsx b/components/DuplicateEventModal.tsx index e9b4866..ef78854 100644 --- a/components/DuplicateEventModal.tsx +++ b/components/DuplicateEventModal.tsx @@ -116,8 +116,7 @@ export const DuplicateEventModal: React.FC = ({ onClose })