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 { ExclamationIcon } from "@heroicons/react/solid";
|
||||||
import { SchedulingType } from "@prisma/client";
|
import { SchedulingType } from "@prisma/client";
|
||||||
|
import { Dayjs } from "dayjs";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React from "react";
|
import React, { FC } from "react";
|
||||||
|
|
||||||
import { useSlots } from "@lib/hooks/useSlots";
|
import { useSlots } from "@lib/hooks/useSlots";
|
||||||
|
|
||||||
import Loader from "@components/Loader";
|
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,
|
date,
|
||||||
eventLength,
|
eventLength,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
|
@ -28,6 +46,7 @@ const AvailableTimes = ({
|
||||||
workingHours,
|
workingHours,
|
||||||
users,
|
users,
|
||||||
minimumBookingNotice,
|
minimumBookingNotice,
|
||||||
|
eventTypeId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { User, SchedulingType } from "@prisma/client";
|
import { Availability, SchedulingType } from "@prisma/client";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import isBetween from "dayjs/plugin/isBetween";
|
import isBetween from "dayjs/plugin/isBetween";
|
||||||
import utc from "dayjs/plugin/utc";
|
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";
|
import getSlots from "@lib/slots";
|
||||||
|
|
||||||
|
@ -13,12 +14,8 @@ dayjs.extend(utc);
|
||||||
|
|
||||||
type AvailabilityUserResponse = {
|
type AvailabilityUserResponse = {
|
||||||
busy: FreeBusyTime;
|
busy: FreeBusyTime;
|
||||||
workingHours: {
|
timeZone: string;
|
||||||
daysOfWeek: number[];
|
workingHours: Availability[];
|
||||||
timeZone: string;
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Slot = {
|
type Slot = {
|
||||||
|
@ -28,15 +25,20 @@ type Slot = {
|
||||||
|
|
||||||
type UseSlotsProps = {
|
type UseSlotsProps = {
|
||||||
eventLength: number;
|
eventLength: number;
|
||||||
|
eventTypeId: number;
|
||||||
minimumBookingNotice?: number;
|
minimumBookingNotice?: number;
|
||||||
date: Dayjs;
|
date: Dayjs;
|
||||||
workingHours: [];
|
workingHours: {
|
||||||
users: User[];
|
days: number[];
|
||||||
schedulingType: SchedulingType;
|
startTime: number;
|
||||||
|
endTime: number;
|
||||||
|
}[];
|
||||||
|
users: { username: string | null }[];
|
||||||
|
schedulingType: SchedulingType | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSlots = (props: UseSlotsProps) => {
|
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 [slots, setSlots] = useState<Slot[]>([]);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<Error | null>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
@ -46,12 +48,13 @@ export const useSlots = (props: UseSlotsProps) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const dateFrom = encodeURIComponent(date.startOf("day").format());
|
const dateFrom = date.startOf("day").format();
|
||||||
const dateTo = encodeURIComponent(date.endOf("day").format());
|
const dateTo = date.endOf("day").format();
|
||||||
|
const query = stringify({ dateFrom, dateTo, eventTypeId });
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
users.map((user: User) =>
|
users.map((user) =>
|
||||||
fetch(`/api/availability/${user.username}?dateFrom=${dateFrom}&dateTo=${dateTo}`)
|
fetch(`/api/availability/${user.username}?${query}`)
|
||||||
.then(handleAvailableSlots)
|
.then(handleAvailableSlots)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -61,7 +64,7 @@ export const useSlots = (props: UseSlotsProps) => {
|
||||||
).then((results) => {
|
).then((results) => {
|
||||||
let loadedSlots: Slot[] = results[0];
|
let loadedSlots: Slot[] = results[0];
|
||||||
if (results.length === 1) {
|
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);
|
setSlots(loadedSlots);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
|
@ -102,19 +105,12 @@ export const useSlots = (props: UseSlotsProps) => {
|
||||||
|
|
||||||
const handleAvailableSlots = async (res) => {
|
const handleAvailableSlots = async (res) => {
|
||||||
const responseBody: AvailabilityUserResponse = await res.json();
|
const responseBody: AvailabilityUserResponse = await res.json();
|
||||||
|
|
||||||
const workingHours = {
|
|
||||||
days: responseBody.workingHours.daysOfWeek,
|
|
||||||
startTime: responseBody.workingHours.startTime,
|
|
||||||
endTime: responseBody.workingHours.endTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
const times = getSlots({
|
const times = getSlots({
|
||||||
frequency: eventLength,
|
frequency: eventLength,
|
||||||
inviteeDate: date,
|
inviteeDate: date,
|
||||||
workingHours: [workingHours],
|
workingHours: responseBody.workingHours,
|
||||||
minimumBookingNotice,
|
minimumBookingNotice,
|
||||||
organizerTimeZone: responseBody.workingHours.timeZone,
|
organizerTimeZone: responseBody.timeZone,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for conflicts
|
// Check for conflicts
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
const user = asStringOrNull(req.query.user);
|
const user = asStringOrNull(req.query.user);
|
||||||
const dateFrom = dayjs(asStringOrNull(req.query.dateFrom));
|
const dateFrom = dayjs(asStringOrNull(req.query.dateFrom));
|
||||||
const dateTo = dayjs(asStringOrNull(req.query.dateTo));
|
const dateTo = dayjs(asStringOrNull(req.query.dateTo));
|
||||||
|
const eventTypeId = parseInt(asStringOrNull(req.query.eventTypeId) || "");
|
||||||
|
|
||||||
if (!dateFrom.isValid() || !dateTo.isValid()) {
|
if (!dateFrom.isValid() || !dateTo.isValid()) {
|
||||||
return res.status(400).json({ message: "Invalid time range given." });
|
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");
|
if (!rawUser) throw new Error("No user found");
|
||||||
|
|
||||||
const { selectedCalendars, ...currentUser } = rawUser;
|
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(),
|
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({
|
res.status(200).json({
|
||||||
busy: bufferedBusyTimes,
|
busy: bufferedBusyTimes,
|
||||||
workingHours: {
|
timeZone,
|
||||||
daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
|
workingHours,
|
||||||
timeZone: currentUser.timeZone,
|
|
||||||
startTime: currentUser.startTime,
|
|
||||||
endTime: currentUser.endTime,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue