From f0bbc37fee287b1175ab4aa816b57c2b489f49ac Mon Sep 17 00:00:00 2001 From: Tim Yu Date: Thu, 8 May 2025 21:47:23 -0500 Subject: [PATCH 01/13] Frontend deadline creation and deadline generation implemented --- frontend/src/app/dashboard/calendar/page.tsx | 195 ++++++++++++++----- 1 file changed, 144 insertions(+), 51 deletions(-) diff --git a/frontend/src/app/dashboard/calendar/page.tsx b/frontend/src/app/dashboard/calendar/page.tsx index e18b065c..7aaf25cb 100644 --- a/frontend/src/app/dashboard/calendar/page.tsx +++ b/frontend/src/app/dashboard/calendar/page.tsx @@ -28,7 +28,7 @@ import { EventSourceInput } from '@fullcalendar/core/index.js'; import { useAuth } from '@/components/context/auth/AuthContext'; import axios from 'axios'; import { backendBaseUrl } from '@/lib/utils'; -import { UserEvent } from '@/lib/types'; +import { UserDeadline, UserEvent } from '@/lib/types'; export default function Home() { //imported from the backend user preferences @@ -63,6 +63,7 @@ export default function Home() { const [newEvent, setNewEvent] = useState({ ...example, }); + const [isDeadline, setIsDeadline] = useState(false); useEffect(() => { async function fetchEvents() { @@ -184,6 +185,7 @@ export default function Home() { console.log(error); }); } + //TO DO: function handleDeleteModal(data: { event: { id: string } }) { setShowDeleteModal(true); @@ -220,6 +222,12 @@ export default function Home() { console.log('no user found'); return; } + + if (isDeadline) { + addDeadlineFromEvent() + return; + } + const eventWithUser: UserEvent & { start: Date; end?: Date } = { ...newEvent, user_id: user.uid, @@ -256,11 +264,64 @@ export default function Home() { }); } + // TODO: handle dragging data + function addDeadlineFromEvent() { + console.log('addDeadlineFromEvent called'); + + if (!user) { + console.log('no user found'); + return; + } + + const deadline: UserDeadline = { + id: new Date().getTime().toString(), + user_id: user.uid, + title: newEvent.title, + due_time: newEvent.start_time || newEvent.end_time, + description: newEvent.description, + priority: null, + projected_duration: parseInt(newEvent.location_place || "60"), + created_at: newEvent.created_at, + }; + + //added to test getting deadline name to the backend + axios + .post(backendBaseUrl + `/api/calendar/deadlines`, deadline) + .then((response) => { + console.log('Successfully saved deadline into backend: ', user!.uid); + console.log(response); + }) + .catch((error) => { + console.log(error); + }); + + setShowModal(false); + setNewEvent({ + ...example, + }); + + setIsDeadline(false); + } + function generatePerfectSchedule() { console.log('Generate perfect schedule clicked'); // Add your logic to generate the perfect schedule here // For example, you can call an API endpoint or perform some calculations // and then update the state accordingly. + if (!user) { + console.log('no user found'); + return; + } + + axios + .post(backendBaseUrl + `/api/user/generate-schedule/${user.uid}`) + .then((response) => { + console.log('Successfully generated schedule: ', user!.uid); + console.log(response); + }) + .catch((error) => { + console.log(error); + }); } @@ -431,10 +492,38 @@ export default function Home() { as="h3" className="text-base font-semibold leading-6 text-gray-900" > - Add Event + Add Event or Deadline
+ + - - setNewEvent({ - ...newEvent, - end_time: new Date( - e.target.value - ).toISOString(), - }) - } - placeholder=" End Time" // Does not work - /> - - setNewEvent({ - ...newEvent, - is_recurring: e.target.checked, - }) - } - placeholder=" Recurring" - /> - + {!isDeadline && ( + <> + + setNewEvent({ + ...newEvent, + end_time: new Date( + e.target.value + ).toISOString(), + }) + } + placeholder=" End Time" // Does not work + /> + + setNewEvent({ + ...newEvent, + is_recurring: e.target.checked, + }) + } + placeholder=" Recurring" + /> + + + )} {newEvent.is_recurring && ( )} - {newEvent.is_recurring && ( + {newEvent.is_recurring && !isDeadline && ( )} - {newEvent.is_recurring && ( + {newEvent.is_recurring && !isDeadline && ( <>
-
+
+
From f157a581b81e005d2752ed09a9a2db4481e4790c Mon Sep 17 00:00:00 2001 From: khanhdo05 Date: Fri, 9 May 2025 01:28:31 -0500 Subject: [PATCH 09/13] fixed delete event/deadline and make a text adaptable to dark mode --- backend/src/controllers/EventController.ts | 21 ++++++-- frontend/src/app/dashboard/calendar/page.tsx | 50 +++++++++++++++----- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/backend/src/controllers/EventController.ts b/backend/src/controllers/EventController.ts index dcc6bfed..6634795d 100644 --- a/backend/src/controllers/EventController.ts +++ b/backend/src/controllers/EventController.ts @@ -97,18 +97,31 @@ export default class EventController { } } + // TODO: Once delete deadline is implemented separately in the frontend, remove this nested logic /** * Deletes a specified event. * @param req.params.id ID of the event. */ public static async deleteEvent(req: Request, res: Response) { try { - const event = await prisma.user_events.delete({ + const event = await prisma.user_events.deleteMany({ where: { id: req.params.id }, // Ensure id is uuid }); - res.json(event); - } catch { - res.status(404).json({ error: 'Not found' }); + if (event.count === 0) { + const deadline = await prisma.user_deadlines.deleteMany({ + where: { id: req.params.id }, + }); + if (deadline.count === 0) { + res.status(404).json({ error: 'Did not find event or deadline with such id' }); + } else { + res.json({ message: 'Deleted deadline', count: deadline.count }); + } + } else { + res.json({ message: 'Deleted event', count: event.count }); + } + } catch (error:any) { + console.error(error); + res.status(404).json({ error: error }); } } diff --git a/frontend/src/app/dashboard/calendar/page.tsx b/frontend/src/app/dashboard/calendar/page.tsx index 6603a0ad..7be38a39 100644 --- a/frontend/src/app/dashboard/calendar/page.tsx +++ b/frontend/src/app/dashboard/calendar/page.tsx @@ -10,7 +10,7 @@ import interactionPlugin, { DropArg, } from '@fullcalendar/interaction'; import timeGridPlugin from '@fullcalendar/timegrid'; -import { Fragment, useEffect, useState } from 'react'; +import { Fragment, useCallback, useEffect, useState } from 'react'; import { Dialog, Transition } from '@headlessui/react'; import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'; import { EventSourceInput } from '@fullcalendar/core/index.js'; @@ -36,7 +36,7 @@ export default function Home() { const [allEvents, setAllEvents] = useState([]); const [showModal, setShowModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); - const [idToDelete, setIdToDelete] = useState(null); + const [idToDelete, setIdToDelete] = useState(null); const example: UserEvent = { title: '', end_time: null, @@ -214,16 +214,41 @@ export default function Home() { function handleDeleteModal(data: { event: { id: string } }) { setShowDeleteModal(true); - setIdToDelete(Number(data.event.id)); + setIdToDelete(data.event.id); } - function handleDelete() { - setAllEvents( - allEvents.filter((event) => Number(event.id) !== Number(idToDelete)) - ); - setShowDeleteModal(false); - setIdToDelete(null); + async function handleDelete() { + console.log('handleDelete called for event: ', idToDelete); + + if (!user) { + console.log('no user found'); + return; + } + + if (!idToDelete) { + console.log('no idToDelete found'); + return; + } + + //added to test getting event name to the backend + axios + .delete(backendBaseUrl + `/api/calendar/events/id/${idToDelete}`) + .then((response) => { + console.log('Successfully delete event: ', idToDelete); + console.log(response); + // UI local state + setAllEvents(allEvents.filter((event) => event.id !== idToDelete)); + }) + .catch((error) => { + console.log(error); + window.alert('Failed to delete event, try again another time :('); + }) + .finally(() => { + setShowDeleteModal(false); + setIdToDelete(null); + }); } + function handleCloseModal() { setShowModal(false); setNewEvent({ @@ -356,9 +381,6 @@ export default function Home() { function generatePerfectSchedule() { console.log('Generate perfect schedule clicked'); - // Add your logic to generate the perfect schedule here - // For example, you can call an API endpoint or perform some calculations - // and then update the state accordingly. if (!user) { console.log('no user found'); return; @@ -409,7 +431,9 @@ export default function Home() { id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50" > -

Frequent Events

+

+ Frequent Events +

{events.map((event) => (
Date: Fri, 9 May 2025 01:32:22 -0500 Subject: [PATCH 10/13] fix lint --- frontend/src/app/dashboard/calendar/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/dashboard/calendar/page.tsx b/frontend/src/app/dashboard/calendar/page.tsx index 7be38a39..3a008a31 100644 --- a/frontend/src/app/dashboard/calendar/page.tsx +++ b/frontend/src/app/dashboard/calendar/page.tsx @@ -10,7 +10,7 @@ import interactionPlugin, { DropArg, } from '@fullcalendar/interaction'; import timeGridPlugin from '@fullcalendar/timegrid'; -import { Fragment, useCallback, useEffect, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; import { Dialog, Transition } from '@headlessui/react'; import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'; import { EventSourceInput } from '@fullcalendar/core/index.js'; From 2138c222a0ccd3efeb5ea349bc9b4301dfffb1ba Mon Sep 17 00:00:00 2001 From: khanhdo05 Date: Fri, 9 May 2025 02:22:18 -0500 Subject: [PATCH 11/13] added details about events in frontend, fixed integration logic to only set once success, debug wrong id saved --- frontend/src/app/dashboard/calendar/page.tsx | 101 +++++++++++++++++-- 1 file changed, 92 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/dashboard/calendar/page.tsx b/frontend/src/app/dashboard/calendar/page.tsx index 3a008a31..19a3bce6 100644 --- a/frontend/src/app/dashboard/calendar/page.tsx +++ b/frontend/src/app/dashboard/calendar/page.tsx @@ -12,7 +12,7 @@ import interactionPlugin, { import timeGridPlugin from '@fullcalendar/timegrid'; import { Fragment, useEffect, useState } from 'react'; import { Dialog, Transition } from '@headlessui/react'; -import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'; +import { CheckIcon } from '@heroicons/react/20/solid'; import { EventSourceInput } from '@fullcalendar/core/index.js'; import { useAuth } from '@/components/context/auth/AuthContext'; import axios from 'axios'; @@ -37,6 +37,7 @@ export default function Home() { const [showModal, setShowModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [idToDelete, setIdToDelete] = useState(null); + const example: UserEvent = { title: '', end_time: null, @@ -53,6 +54,8 @@ export default function Home() { recurrence_pattern: null, recurrence_start_date: null, }; + const [selectedEvent, setSelectedEvent] = useState(example); + const [newEvent, setNewEvent] = useState({ ...example, }); @@ -212,9 +215,15 @@ export default function Home() { }); } - function handleDeleteModal(data: { event: { id: string } }) { + function handleOpenModal(data: { event: { id: string } }) { setShowDeleteModal(true); setIdToDelete(data.event.id); + console.log(data.event.id); + console.log(allEvents); + const selected = allEvents.find((event) => event.id === data.event.id); + if (selected) { + setSelectedEvent(selected); + } } async function handleDelete() { @@ -246,6 +255,7 @@ export default function Home() { .finally(() => { setShowDeleteModal(false); setIdToDelete(null); + setSelectedEvent(example); }); } @@ -256,6 +266,7 @@ export default function Home() { }); setShowDeleteModal(false); setIdToDelete(null); + setSelectedEvent(example); } function handleSubmit(e: React.FormEvent) { @@ -280,7 +291,7 @@ export default function Home() { start: new Date(newEvent.start_time), // <-- ensure start is there end: newEvent.end_time ? new Date(newEvent.end_time) : undefined, title: newEvent.title, - id: newEvent.id, // UUIDs are strings + id: newEvent.id, // this is FullCalendar id break_time: newEvent.break_time, start_time: newEvent.start_time, created_at: new Date().toISOString(), @@ -293,12 +304,15 @@ export default function Home() { recurrence_start_date: newEvent.recurrence_start_date, color: colorTypes.eventByUser, }; - setAllEvents([...allEvents, eventWithUser]); axios .post(backendBaseUrl + `/api/calendar/events`, eventWithUser) .then((response) => { console.log('Successfully saved event:', response.data); + // We want to be consistent and use our backend id + const correctId = response.data.id; + const { id, ...restEvent } = eventWithUser; + setAllEvents([...allEvents, { id: correctId, ...restEvent }]); }) .catch((error) => { console.error('Error saving event:', error); @@ -323,7 +337,7 @@ export default function Home() { const deadline: UserDeadline & UserEvent & { start: Date; color?: string } = { ...example, - id: new Date().getTime().toString(), + id: newEvent.id, user_id: user.uid, title: newEvent.title, due_time: newEvent.end_time ? newEvent.end_time : newEvent.start_time, @@ -334,7 +348,6 @@ export default function Home() { start: new Date(newEvent.start_time), color: colorTypes.deadline, }; - setAllEvents([...allEvents, deadline]); //added to test getting deadline name to the backend axios @@ -345,6 +358,10 @@ export default function Home() { user!.uid ); console.log(response); + // We want to be consistent and use our backend id + const correctId = response.data.id; + const { id, ...restEvent } = deadline; + setAllEvents([...allEvents, { id: correctId, ...restEvent }]); }) .catch((error) => { console.log(error); @@ -391,7 +408,11 @@ export default function Home() { .then((response) => { console.log('Successfully generated schedule for user: ', user!.uid); console.log(response); - window.location.reload(); + if (response.data.length !== 0) { + window.location.reload(); + } else { + window.alert('Add some deadlines first!'); + } }) .catch((error) => { console.log(error); @@ -424,7 +445,7 @@ export default function Home() { selectMirror={true} dateClick={handleDateClick} drop={(data) => addEvent(data)} - eventClick={(data) => handleDeleteModal(data)} + eventClick={(data) => handleOpenModal(data)} />
+
+
+ + {selectedEvent?.title + ? selectedEvent?.title + : 'EVENT/DEADLINE DETAILS'} + + + {selectedEvent?.description && ( +

+ Description:{' '} + {selectedEvent.description} +

+ )} + {selectedEvent?.location_place && ( +

+ Location:{' '} + {selectedEvent.location_place} +

+ )} + +

+ Would you like to delete this event? The action is + permanent. +

+
+
+ +
+ + +
+
+ + {/*
@@ -491,6 +569,11 @@ export default function Home() { aria-hidden="true" />
+ {selectedEvent?.description && ( +

+ Description: {selectedEvent.description} +

+ )}
-
+ */}
From 115256a64ec542a2cc099b73d5c6324fe29054bb Mon Sep 17 00:00:00 2001 From: khanhdo05 Date: Fri, 9 May 2025 02:26:12 -0500 Subject: [PATCH 12/13] fix lint --- frontend/src/app/dashboard/calendar/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/dashboard/calendar/page.tsx b/frontend/src/app/dashboard/calendar/page.tsx index 19a3bce6..29311cd6 100644 --- a/frontend/src/app/dashboard/calendar/page.tsx +++ b/frontend/src/app/dashboard/calendar/page.tsx @@ -124,7 +124,7 @@ export default function Home() { fetchSchedule().then(() => { console.log('Fetched deadlines and events for user: ', user?.displayName); }); - }, [user]); + }, [user, colorTypes, allEvents]); useEffect(() => { const draggableEl = document.getElementById('draggable-el'); @@ -312,6 +312,7 @@ export default function Home() { // We want to be consistent and use our backend id const correctId = response.data.id; const { id, ...restEvent } = eventWithUser; + console.log(`Replace ${id} with ${correctId}`); setAllEvents([...allEvents, { id: correctId, ...restEvent }]); }) .catch((error) => { @@ -361,6 +362,7 @@ export default function Home() { // We want to be consistent and use our backend id const correctId = response.data.id; const { id, ...restEvent } = deadline; + console.log(`Replace ${id} with ${correctId}`); setAllEvents([...allEvents, { id: correctId, ...restEvent }]); }) .catch((error) => { From 15a4c3f38ab30fb5b4b1f2ddd10a79558d22a826 Mon Sep 17 00:00:00 2001 From: khanhdo05 Date: Fri, 9 May 2025 02:33:52 -0500 Subject: [PATCH 13/13] debug colorTypes change on every render --- frontend/src/app/dashboard/calendar/page.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/dashboard/calendar/page.tsx b/frontend/src/app/dashboard/calendar/page.tsx index 29311cd6..c6c25b0b 100644 --- a/frontend/src/app/dashboard/calendar/page.tsx +++ b/frontend/src/app/dashboard/calendar/page.tsx @@ -10,7 +10,7 @@ import interactionPlugin, { DropArg, } from '@fullcalendar/interaction'; import timeGridPlugin from '@fullcalendar/timegrid'; -import { Fragment, useEffect, useState } from 'react'; +import { Fragment, useEffect, useMemo, useState } from 'react'; import { Dialog, Transition } from '@headlessui/react'; import { CheckIcon } from '@heroicons/react/20/solid'; import { EventSourceInput } from '@fullcalendar/core/index.js'; @@ -20,11 +20,14 @@ import { backendBaseUrl } from '@/lib/utils'; import { UserDeadline, UserEvent } from '@/lib/types'; export default function Home() { - const colorTypes = { - deadline: '#e11d48', - eventGenerated: '#a1cc76', - eventByUser: '#6e9adb', - }; + const colorTypes = useMemo( + () => ({ + deadline: '#e11d48', + eventGenerated: '#a1cc76', + eventByUser: '#6e9adb', + }), + [] // empty dependency array means this object will remain stable + ); const { user } = useAuth(); const [events] = useState([ { title: 'event 1', id: '1' }, @@ -115,7 +118,7 @@ export default function Home() { console.log('Mapped events for calendar:', extractedEvents); // setting frontend - setAllEvents([...allEvents, ...extractedEvents, ...extractedDeadlines]); + setAllEvents([...extractedEvents, ...extractedDeadlines]); } catch (error) { console.error('Error fetching events:', error); } @@ -124,7 +127,7 @@ export default function Home() { fetchSchedule().then(() => { console.log('Fetched deadlines and events for user: ', user?.displayName); }); - }, [user, colorTypes, allEvents]); + }, [user, colorTypes]); useEffect(() => { const draggableEl = document.getElementById('draggable-el');