From 4cbfb56f76bbc6b7f7ab92f2dd34de53913d76c9 Mon Sep 17 00:00:00 2001 From: "riho.takagi" Date: Thu, 4 Apr 2024 11:38:56 -0400 Subject: [PATCH 1/7] Fix calendar ui view --- .../routes/booking/components/CalendarDatePicker.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/media_commons_booking_app/src/client/routes/booking/components/CalendarDatePicker.tsx b/media_commons_booking_app/src/client/routes/booking/components/CalendarDatePicker.tsx index c149c321..0fe41d79 100644 --- a/media_commons_booking_app/src/client/routes/booking/components/CalendarDatePicker.tsx +++ b/media_commons_booking_app/src/client/routes/booking/components/CalendarDatePicker.tsx @@ -6,7 +6,7 @@ export const CalendarDatePicker = ({ handleChange }) => { title: 'Date', autoHide: true, todayBtn: true, - clearBtn: true, + clearBtn: false, maxDate: new Date('2030-01-01'), minDate: new Date('1950-01-01'), theme: { @@ -20,11 +20,6 @@ export const CalendarDatePicker = ({ handleChange }) => { inputIcon: '', selected: '', }, - icons: { - // () => ReactElement | JSX.Element - prev: () => Previous, - next: () => Next, - }, datepickerClassNames: 'top-12', defaultDate: new Date(), language: 'en', From 6843bcb496f40aa85c8d048fc4afc12e6e17147d Mon Sep 17 00:00:00 2001 From: "riho.takagi" Date: Thu, 4 Apr 2024 16:40:35 -0400 Subject: [PATCH 2/7] Create booking detail email template --- .../client/routes/booking/booking_detail.html | 169 ++++++++++++++++++ media_commons_booking_app/webpack.config.js | 5 + 2 files changed, 174 insertions(+) create mode 100644 media_commons_booking_app/src/client/routes/booking/booking_detail.html diff --git a/media_commons_booking_app/src/client/routes/booking/booking_detail.html b/media_commons_booking_app/src/client/routes/booking/booking_detail.html new file mode 100644 index 00000000..5240a1f1 --- /dev/null +++ b/media_commons_booking_app/src/client/routes/booking/booking_detail.html @@ -0,0 +1,169 @@ + + + + + + +
+
+
+

+

+ Room Number: + +

+ +

+ Email: + +

+

+ Start date: + +

+

+ End date: + +

+

+ Secondary Point of Contact: + +

+

+ NYU N-number: + +

+

+ Net Id: + +

+

+ Phone Number: + +

+

+ Department: + +

+

+ Requestor's Role: + +

+

+ Sponsor First Name: + +

+

+ Sponsor Last Name: + +

+

+ Sponsor Email: + +

+

+ Reservation Title: + +

+

+ Reservation Description: + +

+

+ Expected Attendance: + +

+

+ Attendee Affiliations: + +

+

+ Room setup needed?: + + +

+

+ Chartfield for Room setup?: + +

+

+ Media Services: + + +

+

+ Catering: + +

+

+ Catering Details: + +

+

+ Chartfield for Catering?: + +

+

+ Hire Security: + +

+

+ Chartfield for Hiring Security?: + +

+
+ + diff --git a/media_commons_booking_app/webpack.config.js b/media_commons_booking_app/webpack.config.js index 37a7587d..d84c866d 100755 --- a/media_commons_booking_app/webpack.config.js +++ b/media_commons_booking_app/webpack.config.js @@ -43,6 +43,7 @@ const serverEntry = './src/server/index.ts'; // define appsscript.json file path const copyAppscriptEntry = './appsscript.json'; const copyEmailTemplate = './src/client/routes/booking/approval_email.html'; +const BookinDetailTemplate = './src/client/routes/booking/booking_detail.html'; const copyApprovalEntry = './src/client/routes/approval.html'; const copyRejectedEntry = './src/client/routes/reject.html'; @@ -85,6 +86,10 @@ const copyFilesConfig = { from: copyAppscriptEntry, to: destination, }, + { + from: BookinDetailTemplate, + to: destination, + }, { from: copyEmailTemplate, to: destination, From a24d06ef428c13813313e423a32668a2cae6cdc8 Mon Sep 17 00:00:00 2001 From: "riho.takagi" Date: Thu, 4 Apr 2024 16:41:57 -0400 Subject: [PATCH 3/7] Send confirmation email to second approver after second approval --- media_commons_booking_app/src/server/admin.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/media_commons_booking_app/src/server/admin.ts b/media_commons_booking_app/src/server/admin.ts index 26780b94..7686317b 100644 --- a/media_commons_booking_app/src/server/admin.ts +++ b/media_commons_booking_app/src/server/admin.ts @@ -66,7 +66,6 @@ export const approveBooking = (id: string) => { //TODO: send email to user updateEventPrefix(id, BookingStatusLabel.PRE_APPROVED); - const subject = 'Second Approval Request'; const contents = bookingContents(id); const recipient = getSecondApproverEmail(process.env.BRANCH_NAME); sendHTMLEmail( @@ -80,6 +79,18 @@ export const approveBooking = (id: string) => { } }; +export const bookingTitle = (id: string) => + getActiveSheetValueById(TableNames.BOOKING, id, 16); + +export const sendConfirmationEmail = (id, status) => { + const email = getSecondApproverEmail(process.env.BRANCH_NAME); + const title = bookingTitle(id); + const contents = bookingContents(id); + contents.headerMessage = 'This is confirmation email.'; + console.log('contents', contents); + sendHTMLEmail('booking_detail', contents, email, status, title, ''); +}; + export const approveEvent = (id: string) => { const guestEmail = getActiveSheetValueById( TableNames.BOOKING_STATUS, @@ -93,6 +104,7 @@ export const approveEvent = (id: string) => { eventTitle, 'Your reservation request for Media Commons is approved.' ); + sendConfirmationEmail(id, BookingStatusLabel.APPROVED); updateEventPrefix(id, BookingStatusLabel.APPROVED); inviteUserToCalendarEvent(id, guestEmail); @@ -142,6 +154,7 @@ export const cancel = (id: string) => { eventTitle, 'Your reservation request for Media Commons has been cancelled. For detailed reasons regarding this decision, please contact us at mediacommons.reservations@nyu.edu.' ); + sendConfirmationEmail(id, BookingStatusLabel.CANCELED); updateEventPrefix(id, BookingStatusLabel.CANCELED); }; @@ -188,6 +201,7 @@ export const noShow = (id: string) => { eventTitle, 'You did not check-in for your Media Commons Reservation and have been marked as a no-show.' ); + sendConfirmationEmail(id, BookingStatusLabel.NO_SHOW); updateEventPrefix(id, BookingStatusLabel.NO_SHOW); }; From 15a611d241dc813bb7179cb9238623b47470c847 Mon Sep 17 00:00:00 2001 From: "riho.takagi" Date: Thu, 4 Apr 2024 16:57:42 -0400 Subject: [PATCH 4/7] Release the selected event when the date is changed. --- .../src/client/routes/booking/components/Calendars.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/media_commons_booking_app/src/client/routes/booking/components/Calendars.tsx b/media_commons_booking_app/src/client/routes/booking/components/Calendars.tsx index bcb4d314..be2606ae 100644 --- a/media_commons_booking_app/src/client/routes/booking/components/Calendars.tsx +++ b/media_commons_booking_app/src/client/routes/booking/components/Calendars.tsx @@ -76,7 +76,10 @@ export const Calendars = ({ const handleChange = (selectedDate: Date) => { allRooms.forEach((room) => { - room.calendarRef.current.getApi().gotoDate(selectedDate); + const roomApi = room.calendarRef.current.getApi(); + roomApi.unselect(); + setBookingTimeEvent(null); + roomApi.gotoDate(selectedDate); }); }; From 257d773663dd0a4df6194f4949bb9d38665b9c8a Mon Sep 17 00:00:00 2001 From: "riho.takagi" Date: Thu, 4 Apr 2024 17:29:15 -0400 Subject: [PATCH 5/7] Include booking details in the request email --- .../routes/booking/hooks/useSubmitBooking.tsx | 11 ++-- media_commons_booking_app/src/server/admin.ts | 66 +++++++++++-------- media_commons_booking_app/src/server/index.ts | 3 +- 3 files changed, 46 insertions(+), 34 deletions(-) diff --git a/media_commons_booking_app/src/client/routes/booking/hooks/useSubmitBooking.tsx b/media_commons_booking_app/src/client/routes/booking/hooks/useSubmitBooking.tsx index 7b15fe6c..30bfcacd 100644 --- a/media_commons_booking_app/src/client/routes/booking/hooks/useSubmitBooking.tsx +++ b/media_commons_booking_app/src/client/routes/booking/hooks/useSubmitBooking.tsx @@ -157,12 +157,15 @@ export default function useSubmitBooking(): [ alert('Your request has been sent.'); navigate('/'); - serverFunctions.sendTextEmail( + const headerMessage = + 'Your reservation is not yet confirmed. The coordinator will review and finalize your reservation within a few days.'; + serverFunctions.sendBookingDetailEmail( + calendarEventId, email, - BookingStatusLabel.REQUESTED, - data.title, - 'Your reservation is not yet confirmed. The coordinator will review and finalize your reservation within a few days.' + headerMessage, + BookingStatusLabel.REQUESTED ); + setLoading(false); reloadBookings(); reloadBookingStatuses(); diff --git a/media_commons_booking_app/src/server/admin.ts b/media_commons_booking_app/src/server/admin.ts index 7686317b..013686ba 100644 --- a/media_commons_booking_app/src/server/admin.ts +++ b/media_commons_booking_app/src/server/admin.ts @@ -63,7 +63,6 @@ export const approveBooking = (id: string) => { } else { firstApprove(id); - //TODO: send email to user updateEventPrefix(id, BookingStatusLabel.PRE_APPROVED); const contents = bookingContents(id); @@ -84,9 +83,14 @@ export const bookingTitle = (id: string) => export const sendConfirmationEmail = (id, status) => { const email = getSecondApproverEmail(process.env.BRANCH_NAME); + const headerMessage = 'This is confirmation email.'; + sendBookingDetailEmail(id, email, headerMessage, status); +}; + +export const sendBookingDetailEmail = (id, email, headerMessage, status) => { const title = bookingTitle(id); const contents = bookingContents(id); - contents.headerMessage = 'This is confirmation email.'; + contents.headerMessage = headerMessage; console.log('contents', contents); sendHTMLEmail('booking_detail', contents, email, status, title, ''); }; @@ -97,12 +101,14 @@ export const approveEvent = (id: string) => { id, ActiveSheetBookingStatusColumns.EMAIL ); - const eventTitle = getActiveSheetValueById(TableNames.BOOKING, id, 16); - sendTextEmail( + + const headerMessage = + 'Your reservation request for Media Commons is approved.'; + sendBookingDetailEmail( + id, guestEmail, - BookingStatusLabel.APPROVED, - eventTitle, - 'Your reservation request for Media Commons is approved.' + headerMessage, + BookingStatusLabel.APPROVED ); sendConfirmationEmail(id, BookingStatusLabel.APPROVED); @@ -123,13 +129,13 @@ export const reject = (id: string) => { id, ActiveSheetBookingStatusColumns.EMAIL ); - const eventTitle = getActiveSheetValueById(TableNames.BOOKING, id, 16); - - sendTextEmail( + const headerMessage = + 'Your reservation request for Media Commons has been rejected. For detailed reasons regarding this decision, please contact us at mediacommons.reservations@nyu.edu.'; + sendBookingDetailEmail( + id, guestEmail, - BookingStatusLabel.REJECTED, - eventTitle, - 'Your reservation request for Media Commons has been rejected. For detailed reasons regarding this decision, please contact us at mediacommons.reservations@nyu.edu.' + headerMessage, + BookingStatusLabel.REJECTED ); updateEventPrefix(id, BookingStatusLabel.REJECTED); }; @@ -146,13 +152,13 @@ export const cancel = (id: string) => { id, ActiveSheetBookingStatusColumns.EMAIL ); - const eventTitle = getActiveSheetValueById(TableNames.BOOKING, id, 16); - - sendTextEmail( + const headerMessage = + 'Your reservation request for Media Commons has been cancelled. For detailed reasons regarding this decision, please contact us at mediacommons.reservations@nyu.edu.'; + sendBookingDetailEmail( + id, guestEmail, - BookingStatusLabel.CANCELED, - eventTitle, - 'Your reservation request for Media Commons has been cancelled. For detailed reasons regarding this decision, please contact us at mediacommons.reservations@nyu.edu.' + headerMessage, + BookingStatusLabel.CANCELED ); sendConfirmationEmail(id, BookingStatusLabel.CANCELED); updateEventPrefix(id, BookingStatusLabel.CANCELED); @@ -170,13 +176,14 @@ export const checkin = (id: string) => { id, ActiveSheetBookingStatusColumns.EMAIL ); - const eventTitle = getActiveSheetValueById(TableNames.BOOKING, id, 16); - sendTextEmail( + const headerMessage = + 'Your reservation request for Media Commons has been checked in. Thank you for choosing Media Commons.'; + sendBookingDetailEmail( + id, guestEmail, - BookingStatusLabel.CHECKED_IN, - eventTitle, - 'Your reservation request for Media Commons has been checked in. Thank you for choosing Media Commons.' + headerMessage, + BookingStatusLabel.CHECKED_IN ); updateEventPrefix(id, BookingStatusLabel.CHECKED_IN); }; @@ -193,13 +200,14 @@ export const noShow = (id: string) => { id, ActiveSheetBookingStatusColumns.EMAIL ); - const eventTitle = getActiveSheetValueById(TableNames.BOOKING, id, 16); - sendTextEmail( + const headerMessage = + 'You did not check-in for your Media Commons Reservation and have been marked as a no-show.'; + sendBookingDetailEmail( + id, guestEmail, - BookingStatusLabel.NO_SHOW, - eventTitle, - 'You did not check-in for your Media Commons Reservation and have been marked as a no-show.' + headerMessage, + BookingStatusLabel.NO_SHOW ); sendConfirmationEmail(id, BookingStatusLabel.NO_SHOW); updateEventPrefix(id, BookingStatusLabel.NO_SHOW); diff --git a/media_commons_booking_app/src/server/index.ts b/media_commons_booking_app/src/server/index.ts index 9ca89610..578e79e3 100644 --- a/media_commons_booking_app/src/server/index.ts +++ b/media_commons_booking_app/src/server/index.ts @@ -25,6 +25,7 @@ import { noShow, reject, removeFromListByEmail, + sendBookingDetailEmail, } from './admin'; import { sendHTMLEmail, sendTextEmail } from './emails'; @@ -53,7 +54,6 @@ export { // email sendHTMLEmail, sendTextEmail, - // admin approveBooking, reject, @@ -62,4 +62,5 @@ export { noShow, approveInstantBooking, removeFromListByEmail, + sendBookingDetailEmail, }; From 8bf3e7c63612dbc50b797694ce40d7ec5b0feb32 Mon Sep 17 00:00:00 2001 From: lucia <51058748+lucia-gomez@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:14:04 -0400 Subject: [PATCH 6/7] update column names and add type checks --- .../routes/admin/components/Bookings.tsx | 2 +- .../routes/admin/hooks/getBookingStatus.ts | 2 +- .../routes/booking/components/Calendars.tsx | 4 +- .../booking/components/MultipleCalendars.tsx | 10 ++++- .../booking/components/RoomCalendar.tsx | 22 ++++++++-- .../routes/booking/hooks/useSubmitBooking.tsx | 15 ++++--- media_commons_booking_app/src/server/admin.ts | 33 +++++++------- .../src/server/calendars.ts | 44 ++++++++++--------- media_commons_booking_app/src/server/db.ts | 13 +----- media_commons_booking_app/src/server/ui.ts | 22 +++------- media_commons_booking_app/src/types.ts | 12 +++-- 11 files changed, 98 insertions(+), 81 deletions(-) diff --git a/media_commons_booking_app/src/client/routes/admin/components/Bookings.tsx b/media_commons_booking_app/src/client/routes/admin/components/Bookings.tsx index e7e3e8e9..c3c90347 100644 --- a/media_commons_booking_app/src/client/routes/admin/components/Bookings.tsx +++ b/media_commons_booking_app/src/client/routes/admin/components/Bookings.tsx @@ -70,7 +70,7 @@ export const Bookings: React.FC = ({ {!isUserView && ( )} {status} diff --git a/media_commons_booking_app/src/client/routes/admin/hooks/getBookingStatus.ts b/media_commons_booking_app/src/client/routes/admin/hooks/getBookingStatus.ts index 02c53adf..fe7fb9e6 100644 --- a/media_commons_booking_app/src/client/routes/admin/hooks/getBookingStatus.ts +++ b/media_commons_booking_app/src/client/routes/admin/hooks/getBookingStatus.ts @@ -6,7 +6,7 @@ export default function getBookingStatus( ): BookingStatusLabel { const bookingStatusLabel = () => { const bookingStatusMatch = bookingStatuses.filter( - (row) => row.calendarId === booking.calendarId + (row) => row.calendarEventId === booking.calendarEventId )[0]; if (bookingStatusMatch === undefined) return BookingStatusLabel.UNKNOWN; if (bookingStatusMatch.checkedInAt !== '') { diff --git a/media_commons_booking_app/src/client/routes/booking/components/Calendars.tsx b/media_commons_booking_app/src/client/routes/booking/components/Calendars.tsx index be2606ae..db872a36 100644 --- a/media_commons_booking_app/src/client/routes/booking/components/Calendars.tsx +++ b/media_commons_booking_app/src/client/routes/booking/components/Calendars.tsx @@ -7,9 +7,9 @@ import { RoomSetting } from '../../../../types'; import { formatDate } from '../../../utils/date'; type CalendarProps = { - allRooms: any[]; + allRooms: RoomSetting[]; selectedRooms: RoomSetting[]; - handleSetDate: any; + handleSetDate: (x: DateSelectArg) => void; refs?: any[]; }; diff --git a/media_commons_booking_app/src/client/routes/booking/components/MultipleCalendars.tsx b/media_commons_booking_app/src/client/routes/booking/components/MultipleCalendars.tsx index 57fca21d..f8c6ab9c 100644 --- a/media_commons_booking_app/src/client/routes/booking/components/MultipleCalendars.tsx +++ b/media_commons_booking_app/src/client/routes/booking/components/MultipleCalendars.tsx @@ -1,10 +1,16 @@ import React, { useEffect, useState } from 'react'; import { Calendars } from './Calendars'; +import { DateSelectArg } from '@fullcalendar/core'; import { RoomSetting } from '../../../../types'; import { SelectRooms } from './SelectRooms'; -export const MultipleCalendars = ({ allRooms, handleSetDate }) => { +interface Props { + allRooms: RoomSetting[]; + handleSetDate: (x: DateSelectArg, y: RoomSetting[]) => void; +} + +export const MultipleCalendars = ({ allRooms, handleSetDate }: Props) => { const [calendarRefs, setCalendarRefs] = useState([]); const [loading, setLoading] = useState(true); const [checkedRoomIds, setCheckedRoomIds] = useState([]); @@ -55,7 +61,7 @@ export const MultipleCalendars = ({ allRooms, handleSetDate }) => { } }; - const handleSubmit = (bookInfo) => { + const handleSubmit = (bookInfo: DateSelectArg) => { handleSetDate(bookInfo, checkedRooms); }; diff --git a/media_commons_booking_app/src/client/routes/booking/components/RoomCalendar.tsx b/media_commons_booking_app/src/client/routes/booking/components/RoomCalendar.tsx index 67eeb1ef..35a57f6f 100644 --- a/media_commons_booking_app/src/client/routes/booking/components/RoomCalendar.tsx +++ b/media_commons_booking_app/src/client/routes/booking/components/RoomCalendar.tsx @@ -1,6 +1,11 @@ -import { BookingStatusLabel, CalendarEvent } from '../../../../types'; -import React, { useEffect, useRef, useState } from 'react'; +import { + BookingStatusLabel, + CalendarEvent, + RoomSetting, +} from '../../../../types'; +import React, { useEffect, useState } from 'react'; +import { DateSelectArg } from '@fullcalendar/core'; import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from '@fullcalendar/daygrid'; import googleCalendarPlugin from '@fullcalendar/google-calendar'; @@ -10,6 +15,15 @@ import timeGridPlugin from '@fullcalendar/timegrid'; // a plugin! const TITLE_TAG = '[Click to Delete]'; +interface Props { + allRooms: RoomSetting[]; + bookingTimeEvent: DateSelectArg; + isOverlap: (x: DateSelectArg) => boolean; + room: RoomSetting; + selectedRooms: RoomSetting[]; + setBookingTimeEvent: (x: DateSelectArg) => void; +} + export const RoomCalendar = ({ room, selectedRooms, @@ -17,7 +31,7 @@ export const RoomCalendar = ({ bookingTimeEvent, setBookingTimeEvent, isOverlap, -}) => { +}: Props) => { const [events, setEvents] = useState([]); useEffect(() => { @@ -29,7 +43,7 @@ export const RoomCalendar = ({ fetchCalendarEvents( process.env.CALENDAR_ENV === 'production' ? room.calendarIdProd - : room.calendarId + : room.calendarIdDev ); }, []); diff --git a/media_commons_booking_app/src/client/routes/booking/hooks/useSubmitBooking.tsx b/media_commons_booking_app/src/client/routes/booking/hooks/useSubmitBooking.tsx index 30bfcacd..28208d8f 100644 --- a/media_commons_booking_app/src/client/routes/booking/hooks/useSubmitBooking.tsx +++ b/media_commons_booking_app/src/client/routes/booking/hooks/useSubmitBooking.tsx @@ -1,5 +1,6 @@ import { Booking, + BookingFormDetails, BookingStatusLabel, Inputs, RoomSetting, @@ -49,12 +50,14 @@ export default function useSubmitBooking(): [ if (process.env.CALENDAR_ENV === 'production') { return room.calendarIdProd; } else { - return room.calendarId; + return room.calendarIdDev; } }; - const sendApprovalEmail = (recipients: string[], contents: Booking) => { - var subject = 'Approval Request'; + const sendApprovalEmail = ( + recipients: string[], + contents: BookingFormDetails + ) => { recipients.forEach((recipient) => serverFunctions.sendHTMLEmail( 'approval_email', @@ -140,10 +143,10 @@ export default function useSubmitBooking(): [ const getApprovalUrl = serverFunctions.approvalUrl(calendarEventId); const getRejectedUrl = serverFunctions.rejectUrl(calendarEventId); Promise.all([getApprovalUrl, getRejectedUrl]).then((values) => { - const userEventInputs: Booking = { - calendarEventId: calendarEventId, + const userEventInputs: BookingFormDetails = { + calendarEventId, roomId: selectedRoomIds, - email: email, + email, startDate: bookingCalendarInfo?.startStr, endDate: bookingCalendarInfo?.endStr, approvalUrl: values[0], diff --git a/media_commons_booking_app/src/server/admin.ts b/media_commons_booking_app/src/server/admin.ts index 013686ba..6b8c2b8f 100644 --- a/media_commons_booking_app/src/server/admin.ts +++ b/media_commons_booking_app/src/server/admin.ts @@ -3,6 +3,7 @@ import { TableNames, getSecondApproverEmail, } from '../policy'; +import { BookingFormDetails, BookingStatusLabel } from '../types'; import { approvalUrl, rejectUrl } from './ui'; import { fetchById, @@ -12,13 +13,11 @@ import { updateActiveSheetValueById, } from './db'; import { inviteUserToCalendarEvent, updateEventPrefix } from './calendars'; -import { sendHTMLEmail, sendTextEmail } from './emails'; -import { BookingStatusLabel } from '../types'; +import { sendHTMLEmail } from './emails'; -export const bookingContents = (id: string) => { +export const bookingContents = (id: string): BookingFormDetails => { const bookingObj = fetchById(TableNames.BOOKING, id); - bookingObj.calendarEventId = id; bookingObj.approvalUrl = approvalUrl(id); bookingObj.rejectedUrl = rejectUrl(id); return bookingObj; @@ -78,21 +77,24 @@ export const approveBooking = (id: string) => { } }; -export const bookingTitle = (id: string) => - getActiveSheetValueById(TableNames.BOOKING, id, 16); - -export const sendConfirmationEmail = (id, status) => { +export const sendConfirmationEmail = ( + calendarEventId: string, + status: BookingStatusLabel +) => { const email = getSecondApproverEmail(process.env.BRANCH_NAME); - const headerMessage = 'This is confirmation email.'; - sendBookingDetailEmail(id, email, headerMessage, status); + const headerMessage = 'This is a confirmation email.'; + sendBookingDetailEmail(calendarEventId, email, headerMessage, status); }; -export const sendBookingDetailEmail = (id, email, headerMessage, status) => { - const title = bookingTitle(id); - const contents = bookingContents(id); +export const sendBookingDetailEmail = ( + calendarEventId: string, + email: string, + headerMessage: string, + status: BookingStatusLabel +) => { + const contents = bookingContents(calendarEventId); contents.headerMessage = headerMessage; - console.log('contents', contents); - sendHTMLEmail('booking_detail', contents, email, status, title, ''); + sendHTMLEmail('booking_detail', contents, email, status, contents.title, ''); }; export const approveEvent = (id: string) => { @@ -104,6 +106,7 @@ export const approveEvent = (id: string) => { const headerMessage = 'Your reservation request for Media Commons is approved.'; + console.log('sending booking detail email...'); sendBookingDetailEmail( id, guestEmail, diff --git a/media_commons_booking_app/src/server/calendars.ts b/media_commons_booking_app/src/server/calendars.ts index 324a0d5e..485ca646 100644 --- a/media_commons_booking_app/src/server/calendars.ts +++ b/media_commons_booking_app/src/server/calendars.ts @@ -1,8 +1,3 @@ -// export const getEvents = async (calendarId: string, startCalendarDate, endCalendarDate) => { -// const calendar = CalendarApp.getCalendarById(calendarId); -// const startDate = new Date(startCalendarDate); -// const endDate = new Date(endCalendarDate); - import { RoomSetting } from '../types'; import { TableNames } from '../policy'; import { getAllActiveSheetRows } from './db'; @@ -36,8 +31,8 @@ export const addEventToCalendar = ( return event.getId(); }; -export const confirmEvent = (calendarId: string) => { - const event = CalendarApp.getEventById(calendarId); +export const confirmEvent = (calendarEventId: string) => { + const event = CalendarApp.getEventById(calendarEventId); event.setTitle(event.getTitle().replace('[HOLD]', '[CONFIRMED]')); // @ts-expect-error GAS type doesn't match the documentation event.setColor(CalendarApp.EventColor.GREEN); @@ -61,35 +56,44 @@ export const getCalendarEvents = (calendarId: string) => { return formattedEvents; }; -const allRoomIds = () => { +const getAllRoomCalendarIds = (): string[] => { const rows = getAllActiveSheetRows(TableNames.ROOMS); - const ids = JSON.parse(rows).map((row: RoomSetting) => row.calendarId); + const ids = JSON.parse(rows).map((room: RoomSetting) => + process.env.CALENDAR_ENV === 'production' + ? room.calendarIdProd + : room.calendarIdDev + ); return ids; }; export const inviteUserToCalendarEvent = ( - eventId: string, + calendarEventId: string, guestEmail: string ) => { console.log(`Invite User: ${guestEmail}`); //TODO: getting roomId from booking sheet - const roomIds = allRoomIds(); - roomIds.forEach((roomId) => { - const calendar = CalendarApp.getCalendarById(roomId); - const event = calendar.getEventById(eventId); + const roomCalendarIds = getAllRoomCalendarIds(); + roomCalendarIds.forEach((roomCalendarId) => { + const calendar = CalendarApp.getCalendarById(roomCalendarId); + const event = calendar.getEventById(calendarEventId); if (event) { event.addGuest(guestEmail); - console.log(`Invited ${guestEmail} to room: ${roomId} event: ${eventId}`); + console.log( + `Invited ${guestEmail} to room: ${roomCalendarId} event: ${calendarEventId}` + ); } }); }; -export const updateEventPrefix = (id: string, newPrefix: string) => { - const roomIds = allRoomIds(); +export const updateEventPrefix = ( + calendarEventId: string, + newPrefix: string +) => { + const roomCalendarIds = getAllRoomCalendarIds(); //TODO: getting roomId from booking sheet - roomIds.map((roomId) => { - const calendar = CalendarApp.getCalendarById(roomId); - const event = calendar.getEventById(id); + roomCalendarIds.map((roomCalendarId) => { + const calendar = CalendarApp.getCalendarById(roomCalendarId); + const event = calendar.getEventById(calendarEventId); const description = ' Cancellation Policy: To cancel reservations please email the Media Commons Team(mediacommons.reservations@nyu.edu) at least 24 hours before the date of the event. Failure to cancel may result in restricted use of event spaces.'; if (event) { diff --git a/media_commons_booking_app/src/server/db.ts b/media_commons_booking_app/src/server/db.ts index 7c862d0e..c1fd995c 100644 --- a/media_commons_booking_app/src/server/db.ts +++ b/media_commons_booking_app/src/server/db.ts @@ -5,17 +5,6 @@ import { TableNames, } from '../policy'; -function formatHeaderString(header: string): string { - // replace all spaces with underscores - let snakeCase = header.replace(/ /g, '_'); - // convert snake_case_string to camelCaseString - let camelCase = snakeCase.replace(/([-_][a-z])/gi, ($1) => { - return $1.toUpperCase().replace('-', '').replace('_', ''); - }); - // ensure first letter is lowercased - return camelCase.charAt(0).toLowerCase() + camelCase.slice(1); -} - const sheetToStrings = (rows: any[][] | undefined) => (rows || []).map((row) => row.map((cell) => `${cell}`)); @@ -24,7 +13,7 @@ function sheetRowToJSON(headers: string[], row: any[]) { headers .filter((header) => header.length > 0) .forEach((header, index) => { - rowObject[formatHeaderString(header)] = `${row[index]}`; + rowObject[header] = `${row[index]}`; }); return rowObject; } diff --git a/media_commons_booking_app/src/server/ui.ts b/media_commons_booking_app/src/server/ui.ts index 59fc998e..e5b2b0fd 100644 --- a/media_commons_booking_app/src/server/ui.ts +++ b/media_commons_booking_app/src/server/ui.ts @@ -1,26 +1,18 @@ -import { appendRowActive, getAllActiveSheetRows } from './db'; import { approveBooking, reject } from './admin'; -import { TableNames } from '../policy'; - -// export const addBookingStatusRequest = (calendarId: string, email: string) => { -// const row = [calendarId, email, new Date()]; -// appendRowActive(TableNames.BOOKING_STATUS, row); -// }; - export const scriptURL = () => { const url = ScriptApp.getService().getUrl(); return url; }; -export const approvalUrl = (calendarId) => { +export const approvalUrl = (calendarEventId: string) => { const url = ScriptApp.getService().getUrl(); - return `${url}?action=approve&page=admin&calendarId=${calendarId}`; + return `${url}?action=approve&page=admin&calendarEventId=${calendarEventId}`; }; -export const rejectUrl = (calendarId) => { +export const rejectUrl = (calendarEventId: string) => { const url = ScriptApp.getService().getUrl(); - return `${url}?action=reject&page=admin&calendarId=${calendarId}`; + return `${url}?action=reject&page=admin&calendarEventId=${calendarEventId}`; }; export const getActiveUserEmail = () => { @@ -34,13 +26,13 @@ export const getActiveUserEmail = () => { export const doGet = (e) => { console.log('DO GET', JSON.stringify(e)); var action = e.parameter['action']; - var calendarId = e.parameter['calendarId']; + var calendarEventId = e.parameter['calendarEventId']; if (action === 'approve') { - approveBooking(calendarId); + approveBooking(calendarEventId); return HtmlService.createHtmlOutputFromFile('approval'); } else if (action === 'reject') { - reject(calendarId); + reject(calendarEventId); return HtmlService.createHtmlOutputFromFile('reject'); } diff --git a/media_commons_booking_app/src/types.ts b/media_commons_booking_app/src/types.ts index 2b4c7589..0f4ec152 100644 --- a/media_commons_booking_app/src/types.ts +++ b/media_commons_booking_app/src/types.ts @@ -9,7 +9,7 @@ export type Ban = { }; export type Booking = Inputs & { - calendarId: string; + calendarEventId: string; email: string; startDate: string; endDate: string; @@ -17,8 +17,14 @@ export type Booking = Inputs & { devBranch: string; }; +export type BookingFormDetails = Booking & { + approvalUrl: string; + rejectUrl: string; + headerMessage?: string; +}; + export type BookingStatus = { - calendarId: string; + calendarEventId: string; email: string; requestedAt: string; firstApprovedAt: string; @@ -115,7 +121,7 @@ export type RoomSetting = { roomId: string; name: string; capacity: string; - calendarId: string; + calendarIdDev: string; calendarIdProd: string; calendarRef?: any; }; From a79e8eb248e944e8a794782ee381cda30fc4af01 Mon Sep 17 00:00:00 2001 From: lucia <51058748+lucia-gomez@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:24:17 -0400 Subject: [PATCH 7/7] add [DEV] and [STAGING] tags to emails --- media_commons_booking_app/src/server/emails.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/media_commons_booking_app/src/server/emails.ts b/media_commons_booking_app/src/server/emails.ts index c03c2a4b..af8169b5 100644 --- a/media_commons_booking_app/src/server/emails.ts +++ b/media_commons_booking_app/src/server/emails.ts @@ -1,4 +1,4 @@ -import { Booking, BookingStatusLabel } from '../types'; +import { Booking, BookingStatusLabel, DevBranch } from '../types'; export const sendTextEmail = ( targetEmail: string, @@ -10,6 +10,17 @@ export const sendTextEmail = ( GmailApp.sendEmail(targetEmail, subj, body); }; +const getEmailBranchTag = () => { + switch (process.env.BRANCH_NAME as DevBranch) { + case 'development': + return '[DEV] '; + case 'staging': + return '[STAGING] '; + default: + return ''; + } +}; + export const sendHTMLEmail = ( templateName: string, contents: Booking, @@ -19,7 +30,7 @@ export const sendHTMLEmail = ( body ) => { console.log('contents', contents); - const subj = `${status}: Media Commons request for \"${eventTitle}\"`; + const subj = `${getEmailBranchTag()}${status}: Media Commons request for \"${eventTitle}\"`; const htmlTemplate = HtmlService.createTemplateFromFile(templateName); for (const key in contents) { htmlTemplate[key] = contents[key] || '';