From a483218985726536d29c6d8f7d85122cb48cd9e5 Mon Sep 17 00:00:00 2001 From: "riho.takagi" Date: Tue, 30 Jan 2024 13:53:15 -0500 Subject: [PATCH] Room Calendar --- .../client/booking/components/Calendars.tsx | 190 +++--------------- .../booking/components/RoomCalendar.tsx | 175 ++++++++++++++++ .../client/booking/components/SheetEditor.tsx | 38 +++- media_commons_booking_app/src/server/index.ts | 2 + media_commons_booking_app/src/server/ui.js | 18 ++ 5 files changed, 252 insertions(+), 171 deletions(-) create mode 100644 media_commons_booking_app/src/client/booking/components/RoomCalendar.tsx diff --git a/media_commons_booking_app/src/client/booking/components/Calendars.tsx b/media_commons_booking_app/src/client/booking/components/Calendars.tsx index e63a002d..0c88cdd0 100644 --- a/media_commons_booking_app/src/client/booking/components/Calendars.tsx +++ b/media_commons_booking_app/src/client/booking/components/Calendars.tsx @@ -1,13 +1,9 @@ import React, { useState, useEffect, useRef } from 'react'; -import interactionPlugin from '@fullcalendar/interaction'; // for selectable -import FullCalendar from '@fullcalendar/react'; -import timeGridPlugin from '@fullcalendar/timegrid'; // a plugin! -import googleCalendarPlugin from '@fullcalendar/google-calendar'; -import dayGridPlugin from '@fullcalendar/daygrid'; import { CalendarDatePicker } from './CalendarDatePicker'; import { DateSelectArg } from '@fullcalendar/core'; import { RoomSetting } from './SheetEditor'; import { formatDate } from '../../utils/date'; +import { RoomCalendar } from './RoomCalendar'; type CalendarProps = { apiKey: string; @@ -25,12 +21,25 @@ export const Calendars = ({ selectedRooms, handleSetDate, }: CalendarProps) => { + console.log('allrooms', allRooms); const [enrolledThisis, setEnrolledThesis] = useState(false); const [bookingTimeEvent, setBookingTimeEvent] = useState(); + const isOverlap = (info) => { + return selectedRooms.some((room, i) => { + const calendarApi = room.calendarRef.current.getApi(); - const editableEvent = (info) => { - return info.title.includes(TITLE_TAG); + const allEvents = calendarApi.getEvents(); + return allEvents.some((event) => { + if (event.title.includes(TITLE_TAG)) return false; + return ( + (event.start >= info.start && event.start < info.end) || + (event.end > info.start && event.end <= info.end) || + (event.start <= info.start && event.end >= info.end) + ); + }); + }); }; + const validateEvents = (e) => { e.stopPropagation; const overlap = isOverlap(bookingTimeEvent); @@ -56,43 +65,6 @@ export const Calendars = ({ } }; - const isOverlap = (info) => { - return selectedRooms.some((room, i) => { - const calendarApi = room.calendarRef.current.getApi(); - - const allEvents = calendarApi.getEvents(); - return allEvents.some((event) => { - if (event.title.includes(TITLE_TAG)) return false; - return ( - (event.start >= info.start && event.start < info.end) || - (event.end > info.start && event.end <= info.end) || - (event.start <= info.start && event.end >= info.end) - ); - }); - }); - }; - - const handleEventClick = (info) => { - if (!editableEvent(info.event)) return; - const targetGroupId = info.event.groupId; - const isConfirmed = window.confirm('Do you want to delete this event?'); - - if (isConfirmed) { - allRooms.map((room) => { - if (!room.calendarRef.current) return; - let calendarApi = room.calendarRef.current.getApi(); - const events = calendarApi.getEvents(); - events.map((event) => { - if (event.groupId === targetGroupId) { - event.remove(); - } - }); - }); - setBookingTimeEvent(null); - return; - } - }; - useEffect(() => { const view = selectedRooms.length > 1 ? 'timeGridDay' : 'timeGridDay'; allRooms.map((room) => { @@ -101,63 +73,13 @@ export const Calendars = ({ }); }), [selectedRooms]; - const handleDateSelect = (selectInfo) => { - if (bookingTimeEvent) { - alert('You can only book one time slot per reservation'); - return; - } - allRooms.map((room) => { - console.log('handle datae select room', room); - if (!room.calendarRef.current) return; - let calendarApi = room.calendarRef.current.getApi(); - calendarApi.addEvent({ - id: Date.now(), // Generate a unique ID for the event - start: selectInfo.startStr, - end: selectInfo.endStr, - title: `${TITLE_TAG}`, - groupId: selectInfo.startStr, - }); - }); - setBookingTimeEvent(selectInfo); - }; const handleChange = (selectedDate: Date) => { allRooms.forEach((room) => { room.calendarRef.current.getApi().gotoDate(selectedDate); }); }; - const handleSelectAllow = (selectInfo) => { - console.log('selectInfo', selectInfo); - // only enrolledThesis user can book over 4 hours - if ( - !enrolledThisis && - selectInfo.end.getTime() / 1000 - selectInfo.start.getTime() / 1000 > - 60 * 60 * 4 - ) { - return false; - } - - console.log('isOverlap', !isOverlap(selectInfo)); - return !isOverlap(selectInfo); - }; - const syncEventLengthAcrossCalendars = (changedEvent) => { - allRooms.forEach((room) => { - const targetGroupId = changedEvent.groupId; - if (room.calendarRef.current) { - let calendarApi = room.calendarRef.current.getApi(); - const events = calendarApi.getEvents(); - events.map((event) => { - //All events are retrieved, so change only for the event retrieved this time. - if (event.groupId === targetGroupId) { - event.setStart(changedEvent.start); - event.setEnd(changedEvent.end); - } - }); - } - }); - setBookingTimeEvent(changedEvent); - }; return (
Select date
@@ -195,77 +117,15 @@ export const Calendars = ({
{allRooms.map((room, i) => ( -
- {selectedRooms.includes(room)} - {room.roomId} {room.name} - 1 ? 'timeGridDay' : 'timeGridDay' - } - navLinks={true} - select={function (info) { - handleDateSelect(info); - }} - eventClick={function (info) { - info.jsEvent.preventDefault(); - handleEventClick(info); - }} - eventAllow={(dropLocation, draggedEvent) => { - return editableEvent(draggedEvent); - }} - selectAllow={(e) => handleSelectAllow(e)} - eventResize={(info) => { - syncEventLengthAcrossCalendars(info.event); - }} - eventDrop={(info) => { - syncEventLengthAcrossCalendars(info.event); - }} - /> -
+ ))}
diff --git a/media_commons_booking_app/src/client/booking/components/RoomCalendar.tsx b/media_commons_booking_app/src/client/booking/components/RoomCalendar.tsx new file mode 100644 index 00000000..ca9992c0 --- /dev/null +++ b/media_commons_booking_app/src/client/booking/components/RoomCalendar.tsx @@ -0,0 +1,175 @@ +import React, { useState, useEffect, useRef } from 'react'; +import interactionPlugin from '@fullcalendar/interaction'; // for selectable +import FullCalendar from '@fullcalendar/react'; +import timeGridPlugin from '@fullcalendar/timegrid'; // a plugin! +import googleCalendarPlugin from '@fullcalendar/google-calendar'; +import dayGridPlugin from '@fullcalendar/daygrid'; +import { serverFunctions } from '../../utils/serverFunctions'; + +const TITLE_TAG = '[Click to Delete]'; + +export const RoomCalendar = ({ + room, + selectedRooms, + allRooms, + bookingTimeEvent, + setBookingTimeEvent, + enrolledThisis, + isOverlap, +}) => { + const [events, setEvents] = useState([]); + + useEffect(() => { + fetchCalendarEvents(room.calendarIdProd); + }, []); + const fetchCalendarEvents = async (calendarId) => { + serverFunctions.getCalendarEvents(calendarId).then((rows) => { + console.log('rows', rows); + setEvents(rows); + }); + }; + + const handleEventClick = (info) => { + if (!editableEvent(info.event)) return; + const targetGroupId = info.event.groupId; + const isConfirmed = window.confirm('Do you want to delete this event?'); + + if (isConfirmed) { + allRooms.map((room) => { + if (!room.calendarRef.current) return; + let calendarApi = room.calendarRef.current.getApi(); + const events = calendarApi.getEvents(); + events.map((event) => { + if (event.groupId === targetGroupId) { + event.remove(); + } + }); + }); + setBookingTimeEvent(null); + return; + } + }; + const handleDateSelect = (selectInfo) => { + if (bookingTimeEvent) { + alert('You can only book one time slot per reservation'); + return; + } + allRooms.map((room) => { + console.log('handle datae select room', room); + if (!room.calendarRef.current) return; + let calendarApi = room.calendarRef.current.getApi(); + calendarApi.addEvent({ + id: Date.now(), // Generate a unique ID for the event + start: selectInfo.startStr, + end: selectInfo.endStr, + title: `${TITLE_TAG}`, + groupId: selectInfo.startStr, + }); + }); + setBookingTimeEvent(selectInfo); + }; + const handleSelectAllow = (selectInfo) => { + console.log('selectInfo', selectInfo); + // only enrolledThesis user can book over 4 hours + if ( + !enrolledThisis && + selectInfo.end.getTime() / 1000 - selectInfo.start.getTime() / 1000 > + 60 * 60 * 4 + ) { + return false; + } + + console.log('isOverlap', !isOverlap(selectInfo)); + return !isOverlap(selectInfo); + }; + + const syncEventLengthAcrossCalendars = (changedEvent) => { + allRooms.forEach((room) => { + const targetGroupId = changedEvent.groupId; + if (room.calendarRef.current) { + let calendarApi = room.calendarRef.current.getApi(); + const events = calendarApi.getEvents(); + events.map((event) => { + //All events are retrieved, so change only for the event retrieved this time. + if (event.groupId === targetGroupId) { + event.setStart(changedEvent.start); + event.setEnd(changedEvent.end); + } + }); + } + }); + setBookingTimeEvent(changedEvent); + }; + const editableEvent = (info) => { + return info.title.includes(TITLE_TAG); + }; + return ( +
+ {selectedRooms.includes(room)} + {room.roomId} {room.name} + 1 ? 'timeGridDay' : 'timeGridDay'} + navLinks={true} + select={function (info) { + handleDateSelect(info); + }} + eventClick={function (info) { + info.jsEvent.preventDefault(); + handleEventClick(info); + }} + eventAllow={(dropLocation, draggedEvent) => { + return editableEvent(draggedEvent); + }} + selectAllow={(e) => handleSelectAllow(e)} + eventResize={(info) => { + syncEventLengthAcrossCalendars(info.event); + }} + eventDrop={(info) => { + syncEventLengthAcrossCalendars(info.event); + }} + /> +
+ ); +}; diff --git a/media_commons_booking_app/src/client/booking/components/SheetEditor.tsx b/media_commons_booking_app/src/client/booking/components/SheetEditor.tsx index ebdb9274..2c8cf83f 100644 --- a/media_commons_booking_app/src/client/booking/components/SheetEditor.tsx +++ b/media_commons_booking_app/src/client/booking/components/SheetEditor.tsx @@ -16,11 +16,21 @@ export type RoomSetting = { name: string; capacity: string; calendarId: string; + calendarIdProd: string; calendarRef?: any; }; export type Purpose = 'multipleRoom' | 'motionCapture'; +const SAFETY_TRAINING_REQUIRED_ROOM = [ + '103', + '220', + '221', + '222', + '223', + '224', + '230', +]; const FIRST_APPROVER = ['rh3555@nyu.edu', 'nnp278@nyu.edu']; const ROOM_SHEET_NAME = 'rooms'; const BASE_URL = @@ -31,6 +41,22 @@ const SAFTY_TRAINING_SHEET_NAME = 'safety_training_users'; const INSTANT_APPROVAL_ROOMS = ['221', '222', '223', '224']; const SheetEditor = () => { + //IN PRODUCTION + //const roomCalendarId = (room) => { + // return findByRoomId(mappingRoomSettings, room.roomId)?.calendarIdProd; + //}; + + //IN DEV + const roomCalendarId = (room) => { + return findByRoomId(mappingRoomSettings, room.roomId)?.calendarId; + }; + + const fetchCalendarEvents = async (calendarId) => { + serverFunctions.getCalendarEvents(calendarId).then((rows) => { + console.log('calendar events', rows); + }); + }; + const getActiveUserEmail = () => { serverFunctions.getActiveUserEmail().then((response) => { console.log('userEmail response', response); @@ -136,6 +162,7 @@ const SheetEditor = () => { name: values[1], capacity: values[2], calendarId: values[3], + calendarIdProd: values[4], }; }; @@ -170,10 +197,6 @@ const SheetEditor = () => { const email = userEmail || data.missingEmail; selectedRoom.map(async (room) => { // Add the event to the calendar. - const roomCalendarId = findByRoomId( - mappingRoomSettings, - room.roomId - )?.calendarId; const calendarEventId = await serverFunctions.addEventToCalendar( roomCalendarId, `[REQUESTED] ${room.roomId} ${data.department} - ${data.firstName} ${data.lastName} (${data.netId})`, @@ -245,8 +268,11 @@ const SheetEditor = () => { const handleSetDate = (info, rooms) => { setBookInfo(info); setSelectedRoom(rooms); - if (userEmail && !isSafetyTrained) { - alert('You have to take safty training before booking!'); + const requiresSafetyTraining = rooms.some((room) => + SAFETY_TRAINING_REQUIRED_ROOM.includes(room.roomId) + ); + if (userEmail && !isSafetyTrained && requiresSafetyTraining) { + alert('You have to take safety training before booking!'); return; } if (userEmail && isBanned) { diff --git a/media_commons_booking_app/src/server/index.ts b/media_commons_booking_app/src/server/index.ts index df5554bd..643fd85e 100644 --- a/media_commons_booking_app/src/server/index.ts +++ b/media_commons_booking_app/src/server/index.ts @@ -7,6 +7,7 @@ import { request, getGoogleCalendarApiKey, getUserEmail, + getCalendarEvents, } from './ui'; import { @@ -73,4 +74,5 @@ export { getUserEmail, removeFromList, getFutureDates, + getCalendarEvents, }; diff --git a/media_commons_booking_app/src/server/ui.js b/media_commons_booking_app/src/server/ui.js index 098bf366..86d97d81 100644 --- a/media_commons_booking_app/src/server/ui.js +++ b/media_commons_booking_app/src/server/ui.js @@ -7,6 +7,24 @@ export const getGoogleCalendarApiKey = () => { return apiKey; }; +export const getCalendarEvents = (calendarId) => { + console.log(calendarId); + var calendar = CalendarApp.getCalendarById(calendarId); + var now = new Date(); + var twoHoursFromNow = new Date(now.getTime() + 120 * 60 * 60 * 1000); + console.log(twoHoursFromNow); + var events = calendar.getEvents(now, twoHoursFromNow); + + const formattedEvents = events.map((e) => { + return { + title: e.getTitle(), + start: e.getStartTime().toISOString(), + end: e.getEndTime().toISOString(), + }; + }); + console.log('formattedEvents', formattedEvents[0]); + return formattedEvents; +}; export const request = (id, email) => { const row = [id, email, new Date()]; SpreadsheetApp.openById(ACTIVE_SHEET_ID)