diff --git a/components/booking/AvailableTimes.tsx b/components/booking/AvailableTimes.tsx index 66bdc43e..6745ddc5 100644 --- a/components/booking/AvailableTimes.tsx +++ b/components/booking/AvailableTimes.tsx @@ -1,17 +1,18 @@ import Link from "next/link"; import { useRouter } from "next/router"; import Slots from "./Slots"; +import {ExclamationIcon} from "@heroicons/react/solid"; -const AvailableTimes = ({ date, eventLength, eventTypeId, workingHours, timeFormat }) => { +const AvailableTimes = ({ date, eventLength, eventTypeId, workingHours, timeFormat, user }) => { const router = useRouter(); - const { user, rescheduleUid } = router.query; - const { slots, isFullyBooked } = Slots({ date, eventLength, workingHours }); + const { rescheduleUid } = router.query; + const { slots, isFullyBooked, hasErrors } = Slots({ date, eventLength, workingHours }); return (
{date.format("dddd DD MMMM YYYY")}
- {slots.length > 0 ? ( + {slots.length > 0 && ( slots.map((slot) => (
)) - ) : isFullyBooked ? -
-

{user} is all booked today.

+ )} + {isFullyBooked &&
+

{user.name} is all booked today.

+
} + + {!isFullyBooked && slots.length === 0 && !hasErrors &&
} + + {hasErrors && ( +
+
+
+
+
+

+ Could not load the available time slots.{" "} + + Contact {user.name} via e-mail + +

+
- :
- } +
+ )}
); }; diff --git a/components/booking/Slots.tsx b/components/booking/Slots.tsx index 76798e70..e3091e96 100644 --- a/components/booking/Slots.tsx +++ b/components/booking/Slots.tsx @@ -3,7 +3,9 @@ import { useRouter } from "next/router"; import getSlots from "../../lib/slots"; import dayjs, {Dayjs} from "dayjs"; import isBetween from "dayjs/plugin/isBetween"; +import utc from "dayjs/plugin/utc"; dayjs.extend(isBetween); +dayjs.extend(utc); type Props = { eventLength: number; @@ -19,18 +21,25 @@ const Slots = ({ eventLength, minimumBookingNotice, date, workingHours }: Props) const { user } = router.query; const [slots, setSlots] = useState([]); const [isFullyBooked, setIsFullyBooked ] = useState(false); + const [hasErrors, setHasErrors ] = useState(false); useEffect(() => { setSlots([]); setIsFullyBooked(false); + setHasErrors(false); fetch( - `/api/availability/${user}?dateFrom=${date.startOf("day").utc().format()}&dateTo=${date + `/api/availability/${user}?dateFrom=${date.startOf("day").utc().startOf('day').format()}&dateTo=${date .endOf("day") .utc() + .endOf('day') .format()}` ) .then((res) => res.json()) - .then(handleAvailableSlots); + .then(handleAvailableSlots) + .catch( e => { + console.error(e); + setHasErrors(true); + }) }, [date]); const handleAvailableSlots = (busyTimes: []) => { @@ -47,26 +56,24 @@ const Slots = ({ eventLength, minimumBookingNotice, date, workingHours }: Props) // Check for conflicts for (let i = times.length - 1; i >= 0; i -= 1) { busyTimes.forEach((busyTime) => { - const startTime = dayjs(busyTime.start); - const endTime = dayjs(busyTime.end); + + const startTime = dayjs(busyTime.start).utc(); + const endTime = dayjs(busyTime.end).utc(); // Check if start times are the same - if (dayjs(times[i]).format("HH:mm") == startTime.format("HH:mm")) { + if (times[i].utc().format("HH:mm") == startTime.format("HH:mm")) { times.splice(i, 1); } - // Check if time is between start and end times - if (dayjs(times[i]).isBetween(startTime, endTime)) { + else if (times[i].utc().isBetween(startTime, endTime)) { times.splice(i, 1); } - // Check if slot end time is between start and end time - if (dayjs(times[i]).add(eventLength, "minutes").isBetween(startTime, endTime)) { + else if (times[i].utc().add(eventLength, "minutes").isBetween(startTime, endTime)) { times.splice(i, 1); } - // Check if startTime is between slot - if (startTime.isBetween(dayjs(times[i]), dayjs(times[i]).add(eventLength, "minutes"))) { + else if (startTime.isBetween(times[i].utc(), times[i].utc().add(eventLength, "minutes"))) { times.splice(i, 1); } }); @@ -82,6 +89,7 @@ const Slots = ({ eventLength, minimumBookingNotice, date, workingHours }: Props) return { slots, isFullyBooked, + hasErrors, }; }; diff --git a/lib/slots.ts b/lib/slots.ts index 00ffc738..c3dad4b5 100644 --- a/lib/slots.ts +++ b/lib/slots.ts @@ -1,11 +1,11 @@ -import dayjs, { Dayjs } from "dayjs"; +import dayjs, {Dayjs} from "dayjs"; import utc from "dayjs/plugin/utc"; dayjs.extend(utc); interface GetSlotsType { inviteeDate: Dayjs; frequency: number; - workingHours: { [WeekDay]: Boundary[] }; + workingHours: []; minimumBookingNotice?: number; } @@ -17,34 +17,30 @@ interface Boundary { const freqApply: number = (cb, value: number, frequency: number): number => cb(value / frequency) * frequency; const intersectBoundary = (a: Boundary, b: Boundary) => { - if (a.upperBound < b.lowerBound || a.lowerBound > b.upperBound) { + if ( + a.upperBound < b.lowerBound || a.lowerBound > b.upperBound + ) { return; } return { lowerBound: Math.max(b.lowerBound, a.lowerBound), - upperBound: Math.min(b.upperBound, a.upperBound), + upperBound: Math.min(b.upperBound, a.upperBound) }; -}; +} // say invitee is -60,1380, and boundary is -120,240 - the overlap is -60,240 -const getOverlaps = (inviteeBoundary: Boundary, boundaries: Boundary[]) => - boundaries.map((boundary) => intersectBoundary(inviteeBoundary, boundary)).filter(Boolean); +const getOverlaps = (inviteeBoundary: Boundary, boundaries: Boundary[]) => boundaries + .map( + (boundary) => intersectBoundary(inviteeBoundary, boundary) + ).filter(Boolean); const organizerBoundaries = (workingHours: [], inviteeDate: Dayjs, inviteeBounds: Boundary): Boundary[] => { const boundaries: Boundary[] = []; - const startDay: number = +inviteeDate - .utc() - .startOf("day") - .add(inviteeBounds.lowerBound, "minutes") - .format("d"); - const endDay: number = +inviteeDate - .utc() - .startOf("day") - .add(inviteeBounds.upperBound, "minutes") - .format("d"); + const startDay: number = +inviteeDate.utc().startOf('day').add(inviteeBounds.lowerBound, 'minutes').format('d'); + const endDay: number = +inviteeDate.utc().startOf('day').add(inviteeBounds.upperBound, 'minutes').format('d'); - workingHours.forEach((item) => { + workingHours.forEach( (item) => { const lowerBound: number = item.startTime; const upperBound: number = lowerBound + item.length; if (startDay !== endDay) { @@ -66,7 +62,7 @@ const organizerBoundaries = (workingHours: [], inviteeDate: Dayjs, inviteeBounds } } } else { - boundaries.push({ lowerBound, upperBound }); + boundaries.push({lowerBound, upperBound}); } }); return boundaries; @@ -76,33 +72,38 @@ const inviteeBoundary = (startTime: number, utcOffset: number, frequency: number const upperBound: number = freqApply(Math.floor, 1440 - utcOffset, frequency); const lowerBound: number = freqApply(Math.ceil, startTime - utcOffset, frequency); return { - lowerBound, - upperBound, + lowerBound, upperBound, }; }; -const getSlotsBetweenBoundary = (frequency: number, { lowerBound, upperBound }: Boundary) => { +const getSlotsBetweenBoundary = (frequency: number, {lowerBound,upperBound}: Boundary) => { const slots: Dayjs[] = []; - for (let minutes = 0; lowerBound + minutes <= upperBound - frequency; minutes += frequency) { - slots.push( - dayjs - .utc() - .startOf("day") - .add(lowerBound + minutes, "minutes") - ); + for ( + let minutes = 0; + lowerBound + minutes <= upperBound - frequency; + minutes += frequency + ) { + slots.push(dayjs.utc().startOf('day').add(lowerBound + minutes, 'minutes')); } return slots; }; -const getSlots = ({ inviteeDate, frequency, minimumBookingNotice, workingHours }: GetSlotsType): Dayjs[] => { - const startTime = dayjs.utc().isSame(dayjs(inviteeDate), "day") - ? inviteeDate.hour() * 60 + inviteeDate.minute() + minimumBookingNotice - : 0; +const getSlots = ( + { inviteeDate, frequency, minimumBookingNotice, workingHours, }: GetSlotsType +): Dayjs[] => { + + const startTime = ( + dayjs.utc().isSame(dayjs(inviteeDate), 'day') ? inviteeDate.hour() * 60 + inviteeDate.minute() + minimumBookingNotice : 0 + ); const inviteeBounds = inviteeBoundary(startTime, inviteeDate.utcOffset(), frequency); - return getOverlaps(inviteeBounds, organizerBoundaries(workingHours, inviteeDate, inviteeBounds)) - .reduce((slots, boundary: Boundary) => [...slots, ...getSlotsBetweenBoundary(frequency, boundary)], []) - .map((slot) => slot.utcOffset(dayjs(inviteeDate).utcOffset())); -}; + return getOverlaps( + inviteeBounds, organizerBoundaries(workingHours, inviteeDate, inviteeBounds) + ).reduce( + (slots, boundary: Boundary) => [...slots, ...getSlotsBetweenBoundary(frequency, boundary) ], [] + ).map( + (slot) => slot.utcOffset(dayjs(inviteeDate).utcOffset()) + ) +} export default getSlots; diff --git a/pages/[user]/[type].tsx b/pages/[user]/[type].tsx index 973c8d78..328a2884 100644 --- a/pages/[user]/[type].tsx +++ b/pages/[user]/[type].tsx @@ -122,6 +122,7 @@ export default function Type(props): Type { eventLength={props.eventType.length} eventTypeId={props.eventType.id} date={selectedDate} + user={props.user} /> )}