More availability fixes (#755)
* Abstracts MinutesField * Adds missing Minimum booking notice * Refactoring * Fixes int field sent as string * Sorts slots by time * Fixes availability page * Fixes available days * Type fixes * More availability bugfixes
This commit is contained in:
parent
790ed3e6b1
commit
bcacc1d166
3 changed files with 63 additions and 34 deletions
|
@ -1,14 +1,32 @@
|
|||
import { ExclamationIcon } from "@heroicons/react/solid";
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import { Dayjs } from "dayjs";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useSlots } from "@lib/hooks/useSlots";
|
||||
|
||||
import Loader from "@components/Loader";
|
||||
|
||||
const AvailableTimes = ({
|
||||
type AvailableTimesProps = {
|
||||
workingHours: {
|
||||
days: number[];
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
}[];
|
||||
timeFormat: string;
|
||||
minimumBookingNotice: number;
|
||||
eventTypeId: number;
|
||||
eventLength: number;
|
||||
date: Dayjs;
|
||||
users: {
|
||||
username: string | null;
|
||||
}[];
|
||||
schedulingType: SchedulingType | null;
|
||||
};
|
||||
|
||||
const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
date,
|
||||
eventLength,
|
||||
eventTypeId,
|
||||
|
@ -28,6 +46,7 @@ const AvailableTimes = ({
|
|||
workingHours,
|
||||
users,
|
||||
minimumBookingNotice,
|
||||
eventTypeId,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { User, SchedulingType } from "@prisma/client";
|
||||
import { Availability, SchedulingType } from "@prisma/client";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { useState, useEffect } from "react";
|
||||
import { stringify } from "querystring";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import getSlots from "@lib/slots";
|
||||
|
||||
|
@ -13,12 +14,8 @@ dayjs.extend(utc);
|
|||
|
||||
type AvailabilityUserResponse = {
|
||||
busy: FreeBusyTime;
|
||||
workingHours: {
|
||||
daysOfWeek: number[];
|
||||
timeZone: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
workingHours: Availability[];
|
||||
};
|
||||
|
||||
type Slot = {
|
||||
|
@ -28,15 +25,20 @@ type Slot = {
|
|||
|
||||
type UseSlotsProps = {
|
||||
eventLength: number;
|
||||
eventTypeId: number;
|
||||
minimumBookingNotice?: number;
|
||||
date: Dayjs;
|
||||
workingHours: [];
|
||||
users: User[];
|
||||
schedulingType: SchedulingType;
|
||||
workingHours: {
|
||||
days: number[];
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
}[];
|
||||
users: { username: string | null }[];
|
||||
schedulingType: SchedulingType | null;
|
||||
};
|
||||
|
||||
export const useSlots = (props: UseSlotsProps) => {
|
||||
const { eventLength, minimumBookingNotice = 0, date, users } = props;
|
||||
const { eventLength, minimumBookingNotice = 0, date, users, eventTypeId } = props;
|
||||
const [slots, setSlots] = useState<Slot[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
@ -46,12 +48,13 @@ export const useSlots = (props: UseSlotsProps) => {
|
|||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const dateFrom = encodeURIComponent(date.startOf("day").format());
|
||||
const dateTo = encodeURIComponent(date.endOf("day").format());
|
||||
const dateFrom = date.startOf("day").format();
|
||||
const dateTo = date.endOf("day").format();
|
||||
const query = stringify({ dateFrom, dateTo, eventTypeId });
|
||||
|
||||
Promise.all(
|
||||
users.map((user: User) =>
|
||||
fetch(`/api/availability/${user.username}?dateFrom=${dateFrom}&dateTo=${dateTo}`)
|
||||
users.map((user) =>
|
||||
fetch(`/api/availability/${user.username}?${query}`)
|
||||
.then(handleAvailableSlots)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
|
@ -61,7 +64,7 @@ export const useSlots = (props: UseSlotsProps) => {
|
|||
).then((results) => {
|
||||
let loadedSlots: Slot[] = results[0];
|
||||
if (results.length === 1) {
|
||||
loadedSlots = loadedSlots.sort((a, b) => (a.time.isAfter(b.time) ? 1 : -1));
|
||||
loadedSlots = loadedSlots?.sort((a, b) => (a.time.isAfter(b.time) ? 1 : -1));
|
||||
setSlots(loadedSlots);
|
||||
setLoading(false);
|
||||
return;
|
||||
|
@ -102,19 +105,12 @@ export const useSlots = (props: UseSlotsProps) => {
|
|||
|
||||
const handleAvailableSlots = async (res) => {
|
||||
const responseBody: AvailabilityUserResponse = await res.json();
|
||||
|
||||
const workingHours = {
|
||||
days: responseBody.workingHours.daysOfWeek,
|
||||
startTime: responseBody.workingHours.startTime,
|
||||
endTime: responseBody.workingHours.endTime,
|
||||
};
|
||||
|
||||
const times = getSlots({
|
||||
frequency: eventLength,
|
||||
inviteeDate: date,
|
||||
workingHours: [workingHours],
|
||||
workingHours: responseBody.workingHours,
|
||||
minimumBookingNotice,
|
||||
organizerTimeZone: responseBody.workingHours.timeZone,
|
||||
organizerTimeZone: responseBody.timeZone,
|
||||
});
|
||||
|
||||
// Check for conflicts
|
||||
|
|
|
@ -10,6 +10,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
const user = asStringOrNull(req.query.user);
|
||||
const dateFrom = dayjs(asStringOrNull(req.query.dateFrom));
|
||||
const dateTo = dayjs(asStringOrNull(req.query.dateTo));
|
||||
const eventTypeId = parseInt(asStringOrNull(req.query.eventTypeId) || "");
|
||||
|
||||
if (!dateFrom.isValid() || !dateTo.isValid()) {
|
||||
return res.status(400).json({ message: "Invalid time range given." });
|
||||
|
@ -31,6 +32,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
},
|
||||
});
|
||||
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
select: {
|
||||
timeZone: true,
|
||||
availability: {
|
||||
select: {
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
days: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!rawUser) throw new Error("No user found");
|
||||
|
||||
const { selectedCalendars, ...currentUser } = rawUser;
|
||||
|
@ -49,13 +64,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
end: dayjs(a.end).add(currentUser.bufferTime, "minute").toString(),
|
||||
}));
|
||||
|
||||
const timeZone = eventType?.timeZone || currentUser.timeZone;
|
||||
const workingHours = eventType?.availability.length ? eventType.availability : currentUser.availability;
|
||||
|
||||
res.status(200).json({
|
||||
busy: bufferedBusyTimes,
|
||||
workingHours: {
|
||||
daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
|
||||
timeZone: currentUser.timeZone,
|
||||
startTime: currentUser.startTime,
|
||||
endTime: currentUser.endTime,
|
||||
},
|
||||
timeZone,
|
||||
workingHours,
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue