Skip to content

Commit

Permalink
Merge pull request #180 from ITPNYU/staging
Browse files Browse the repository at this point in the history
Staging release 2024-04-12
  • Loading branch information
nopivnick authored Apr 12, 2024
2 parents e88809b + 9fc052d commit 988ed3e
Show file tree
Hide file tree
Showing 16 changed files with 469 additions and 250 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default function Admin() {
{tab === 'adminUsers' && <AdminUsers />}
{tab === 'paUsers' && <PAUsers />}
{tab === 'liaesons' && <Liaisons />}
{tab === 'bookings' && <Bookings showNnumber={true} />}
{tab === 'bookings' && <Bookings isAdminView={true} />}
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,88 +1,159 @@
import React, { useContext, useMemo, useState } from 'react';
import React, { useContext, useState } from 'react';

import { BookingStatusLabel } from '../../../../types';
import { DatabaseContext } from '../../components/Provider';
import Loading from '../../../utils/Loading';
import { serverFunctions } from '../../../utils/serverFunctions';
import { useLocation } from 'react-router';

interface Props {
calendarEventId: string;
isAdminView: boolean;
isUserView: boolean;
setOptimisticStatus: (x: BookingStatusLabel) => void;
status: BookingStatusLabel;
}

export default function BookingActions({ status, calendarEventId }: Props) {
const [loading, setLoading] = useState(false);
export default function BookingActions({
status,
calendarEventId,
isAdminView,
isUserView,
setOptimisticStatus,
}: Props) {
const [uiLoading, setUiLoading] = useState(false);
const { reloadBookings, reloadBookingStatuses } = useContext(DatabaseContext);

const location = useLocation();
const isAdminPage = useMemo(() => location.pathname === '/admin', [location]);

const reload = async () => {
await Promise.all([reloadBookings(), reloadBookingStatuses()]);
};

const ActionButton = (text: string, action: () => Promise<void>) => (
const onError = () => alert('Failed to perform action on booking');

const ActionButton = (
text: string,
action: () => Promise<void>,
optimisticNextStatus: BookingStatusLabel,
confirmation?: boolean
) => (
<button
className="font-medium text-blue-600 dark:text-blue-500 hover:underline mr-2"
onClick={async () => {
setLoading(true);
if (confirmation) {
const result = confirm(`Are you sure? This action can't be undone.`);
if (!result) {
return;
}
}
setUiLoading(true);
setOptimisticStatus(optimisticNextStatus);
try {
await action();
await reload();
action()
.catch(() => {
onError();
setOptimisticStatus(undefined);
})
.finally(() => reload().then(() => setOptimisticStatus(undefined)));
} catch (ex) {
console.error(ex);
alert('Failed to perform action on booking');
onError();
} finally {
setLoading(false);
setUiLoading(false);
}
}}
>
{text}
</button>
);

if (loading) {
if (uiLoading) {
return (
<td className="px-2 py-4 w-28">
<Loading />
</td>
);
}

const paBtns = (
<>
{status !== BookingStatusLabel.CHECKED_IN &&
ActionButton('Check In', () =>
serverFunctions.checkin(calendarEventId)
)}
{status !== BookingStatusLabel.NO_SHOW &&
ActionButton('No Show', () => serverFunctions.noShow(calendarEventId))}
</>
);
if (isUserView) {
if (status === BookingStatusLabel.CANCELED) {
return <td />;
}
return (
<td className="px-2 py-4 w-28">
<div className="flex flex-col items-start">
{ActionButton(
'Cancel',
() => serverFunctions.cancel(calendarEventId),
BookingStatusLabel.CANCELED,
true
)}
</div>
</td>
);
}

if (!isAdminPage) {
const paBtns = () => {
const checkInBtn = ActionButton(
'Check In',
() => serverFunctions.checkin(calendarEventId),
BookingStatusLabel.CHECKED_IN
);
const noShowBtn = ActionButton(
'No Show',
() => serverFunctions.noShow(calendarEventId),
BookingStatusLabel.NO_SHOW
);

if (status === BookingStatusLabel.APPROVED) {
return (
<>
{checkInBtn}
{noShowBtn}
</>
);
} else if (status === BookingStatusLabel.CHECKED_IN) {
return noShowBtn;
} else if (status === BookingStatusLabel.NO_SHOW) {
return checkInBtn;
}
};

if (!isAdminView) {
return (
<td className="px-2 py-4 w-28">
<div className="flex flex-col items-start">{paBtns}</div>
<div className="flex flex-col items-start">{paBtns()}</div>
</td>
);
}

if (
status === BookingStatusLabel.CANCELED ||
status === BookingStatusLabel.REJECTED
) {
return <td />;
}

return (
<td className="px-2 py-4 w-36">
<td className="px-2 py-4 w-40">
<div className="flex flex-col items-start">
{status === BookingStatusLabel.PRE_APPROVED &&
ActionButton('2nd Approve', () =>
serverFunctions.approveBooking(calendarEventId)
ActionButton(
'2nd Approve',
() => serverFunctions.approveBooking(calendarEventId),
BookingStatusLabel.APPROVED
)}
{status === BookingStatusLabel.REQUESTED &&
ActionButton('1st Approve', () =>
serverFunctions.approveBooking(calendarEventId)
ActionButton(
'1st Approve',
() => serverFunctions.approveBooking(calendarEventId),
BookingStatusLabel.PRE_APPROVED
)}
{ActionButton('Reject', () => serverFunctions.reject(calendarEventId))}
{ActionButton('Cancel', () => serverFunctions.cancel(calendarEventId))}
{paBtns}
{ActionButton(
'Reject',
() => serverFunctions.reject(calendarEventId),
BookingStatusLabel.REJECTED,
true
)}
{paBtns()}
</div>
</td>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Booking, BookingStatusLabel } from '../../../../types';
import React, { useContext, useState } from 'react';

import BookingActions from './BookingActions';
import { DatabaseContext } from '../../components/Provider';
import { formatDate } from '../../../utils/date';
import getBookingStatus from '../hooks/getBookingStatus';

interface Props {
booking: Booking;
isAdminView: boolean;
isUserView: boolean;
}

export default function BookingTableRow({
booking,
isAdminView,
isUserView,
}: Props) {
const { bookingStatuses } = useContext(DatabaseContext);
const status = getBookingStatus(booking, bookingStatuses);

const [optimisticStatus, setOptimisticStatus] =
useState<BookingStatusLabel>();

return (
<tr className="">
<BookingActions
status={optimisticStatus ?? status}
calendarEventId={booking.calendarEventId}
{...{ setOptimisticStatus, isAdminView, isUserView }}
/>
<td className="px-2 py-4 w-28">{optimisticStatus ?? status}</td>
<td className="px-2 py-4 w-36">{booking.roomId}</td>
<td scope="row" className="px-2 py-4 w-40 text-gray-900 dark:text-white">
<div className="pl-3 w-full">
<div className="flex flex-col">
<div className="text-base font-semibold">
{booking.firstName} {booking.lastName}
</div>
<div className="font-normal text-gray-500">{booking.email}</div>
<div className="font-normal text-gray-500">
{booking.phoneNumber}
</div>
</div>
</div>
</td>
<td className="px-2 py-4 w-40">
<div className=" flex items-center flex-col">
<div>{formatDate(booking.startDate)}</div>
</div>
</td>
<td className="px-2 py-4 w-40">
<div className=" flex items-center flex-col">
<div>{formatDate(booking.endDate)}</div>
</div>
</td>
<td className="px-2 py-4 w-36">{booking.secondaryName}</td>
{isAdminView && <td className="px-2 py-4 w-20">{booking.nNumber}</td>}
<td className="px-2 py-4 w-20">{booking.netId}</td>
<td className="px-2 py-4 w-36">{booking.department}</td>
<td className="px-2 py-4 w-20">{booking.role}</td>
<td className="px-2 py-4 w-24">
{booking.sponsorFirstName} {booking.sponsorLastName}
</td>
<td className="px-2 py-4 w-20">{booking.sponsorEmail}</td>
<td className="px-2 py-4 w-52 break-all">{booking.title}</td>
<td className="px-2 py-4 w-60 break-all">{booking.description}</td>
<td className="px-2 py-4 w-20">{booking.expectedAttendance}</td>
<td className="px-2 py-4 w-20">{booking.attendeeAffiliation}</td>
<td className="px-2 py-4 w-40">
{booking.roomSetup}
{booking.setupDetails && (
<>
<br />
<b>Details</b>
<br />
{booking.setupDetails}
</>
)}
</td>
<td className="px-2 py-4 w-24">{booking.chartFieldForRoomSetup}</td>
<td className="px-2 py-4 w-40">
{booking.mediaServices}
{booking.mediaServicesDetails && (
<>
<br />
<b>Details</b>
<br />
{booking.mediaServicesDetails}
</>
)}
</td>
<td className="px-2 py-4 w-18">{booking.catering}</td>
<td className="px-2 py-4 w-18">{booking.cateringService}</td>
<td className="px-2 py-4 w-24">{booking.chartFieldForCatering}</td>
<td className="px-2 py-4 w-18">{booking.hireSecurity}</td>
<td className="px-2 py-4 w-24">{booking.chartFieldForSecurity}</td>
</tr>
);
}
Loading

0 comments on commit 988ed3e

Please sign in to comment.