Fix for buffer not considering custom interval slots and event duration for slots when using custom intervals (#2079)
* modified buffer checks * added custom interval consideration in getSlots fn * further getslot call fixes * added check for end of day availability slots * removed debug remnants * moved slot filtering into a function * improved readability of code * improved readability * extracted getFilteredTimes outside useSlot * added a buffer test * added another buffer test * edge case fix for eod availability and test fix * removed unnecessary comments * verbose comment * fixed eod logic and updated expected test value Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
ada3317ba5
commit
788e2acaff
5 changed files with 152 additions and 51 deletions
|
@ -139,6 +139,7 @@ function DatePicker({
|
|||
frequency: eventLength,
|
||||
minimumBookingNotice,
|
||||
workingHours,
|
||||
eventLength,
|
||||
}).length
|
||||
);
|
||||
};
|
||||
|
|
|
@ -44,6 +44,7 @@ export default function TeamAvailabilityTimes(props: Props) {
|
|||
inviteeDate: props.selectedDate,
|
||||
workingHours: data?.workingHours || [],
|
||||
minimumBookingNotice: 0,
|
||||
eventLength: props.frequency,
|
||||
})
|
||||
: [];
|
||||
|
||||
|
|
|
@ -34,6 +34,70 @@ type UseSlotsProps = {
|
|||
afterBufferTime?: number;
|
||||
};
|
||||
|
||||
type getFilteredTimesProps = {
|
||||
times: dayjs.Dayjs[];
|
||||
busy: TimeRange[];
|
||||
eventLength: number;
|
||||
beforeBufferTime: number;
|
||||
afterBufferTime: number;
|
||||
};
|
||||
|
||||
export const getFilteredTimes = (props: getFilteredTimesProps) => {
|
||||
const { times, busy, eventLength, beforeBufferTime, afterBufferTime } = props;
|
||||
const finalizationTime = times[times.length - 1].add(eventLength, "minutes");
|
||||
// Check for conflicts
|
||||
for (let i = times.length - 1; i >= 0; i -= 1) {
|
||||
// const totalSlotLength = eventLength + beforeBufferTime + afterBufferTime;
|
||||
// Check if the slot surpasses the user's availability end time
|
||||
const slotEndTimeWithAfterBuffer = times[i].add(eventLength + afterBufferTime, "minutes");
|
||||
if (slotEndTimeWithAfterBuffer.isAfter(finalizationTime, "minute")) {
|
||||
times.splice(i, 1);
|
||||
} else {
|
||||
const slotStartTime = times[i];
|
||||
const slotEndTime = times[i].add(eventLength, "minutes");
|
||||
const slotStartTimeWithBeforeBuffer = times[i].subtract(beforeBufferTime, "minutes");
|
||||
busy.every((busyTime): boolean => {
|
||||
const startTime = dayjs(busyTime.start);
|
||||
const endTime = dayjs(busyTime.end);
|
||||
// Check if start times are the same
|
||||
if (slotStartTime.isBetween(startTime, endTime, null, "[)")) {
|
||||
times.splice(i, 1);
|
||||
}
|
||||
// Check if slot end time is between start and end time
|
||||
else if (slotEndTime.isBetween(startTime, endTime)) {
|
||||
times.splice(i, 1);
|
||||
}
|
||||
// Check if startTime is between slot
|
||||
else if (startTime.isBetween(slotStartTime, slotEndTime)) {
|
||||
times.splice(i, 1);
|
||||
}
|
||||
// Check if timeslot has before buffer time space free
|
||||
else if (
|
||||
slotStartTimeWithBeforeBuffer.isBetween(
|
||||
startTime.subtract(beforeBufferTime, "minutes"),
|
||||
endTime.add(afterBufferTime, "minutes")
|
||||
)
|
||||
) {
|
||||
times.splice(i, 1);
|
||||
}
|
||||
// Check if timeslot has after buffer time space free
|
||||
else if (
|
||||
slotEndTimeWithAfterBuffer.isBetween(
|
||||
startTime.subtract(beforeBufferTime, "minutes"),
|
||||
endTime.add(afterBufferTime, "minutes")
|
||||
)
|
||||
) {
|
||||
times.splice(i, 1);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
return times;
|
||||
};
|
||||
|
||||
export const useSlots = (props: UseSlotsProps) => {
|
||||
const {
|
||||
slotInterval,
|
||||
|
@ -118,56 +182,19 @@ export const useSlots = (props: UseSlotsProps) => {
|
|||
inviteeDate: date,
|
||||
workingHours: responseBody.workingHours,
|
||||
minimumBookingNotice,
|
||||
eventLength,
|
||||
});
|
||||
// Check for conflicts
|
||||
for (let i = times.length - 1; i >= 0; i -= 1) {
|
||||
responseBody.busy.every((busyTime): boolean => {
|
||||
const startTime = dayjs(busyTime.start);
|
||||
const endTime = dayjs(busyTime.end);
|
||||
// Check if start times are the same
|
||||
if (times[i].isBetween(startTime, endTime, null, "[)")) {
|
||||
times.splice(i, 1);
|
||||
}
|
||||
// Check if slot end time is between start and end time
|
||||
else if (times[i].add(eventLength, "minutes").isBetween(startTime, endTime)) {
|
||||
times.splice(i, 1);
|
||||
}
|
||||
// Check if startTime is between slot
|
||||
else if (startTime.isBetween(times[i], times[i].add(eventLength, "minutes"))) {
|
||||
times.splice(i, 1);
|
||||
}
|
||||
// Check if time is between afterBufferTime and beforeBufferTime
|
||||
else if (
|
||||
times[i].isBetween(
|
||||
startTime.subtract(beforeBufferTime, "minutes"),
|
||||
endTime.add(afterBufferTime, "minutes")
|
||||
)
|
||||
) {
|
||||
times.splice(i, 1);
|
||||
}
|
||||
// considering preceding event's after buffer time
|
||||
else if (
|
||||
i > 0 &&
|
||||
times[i - 1]
|
||||
.add(eventLength + afterBufferTime, "minutes")
|
||||
.isBetween(
|
||||
startTime.subtract(beforeBufferTime, "minutes"),
|
||||
endTime.add(afterBufferTime, "minutes"),
|
||||
null,
|
||||
"[)"
|
||||
)
|
||||
) {
|
||||
times.splice(i, 1);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
const filterTimeProps = {
|
||||
times,
|
||||
busy: responseBody.busy,
|
||||
eventLength,
|
||||
beforeBufferTime,
|
||||
afterBufferTime,
|
||||
};
|
||||
const filteredTimes = getFilteredTimes(filterTimeProps);
|
||||
// temporary
|
||||
const user = res.url.substring(res.url.lastIndexOf("/") + 1, res.url.indexOf("?"));
|
||||
return times.map((time) => ({
|
||||
return filteredTimes.map((time) => ({
|
||||
time,
|
||||
users: [user],
|
||||
}));
|
||||
|
|
|
@ -15,26 +15,34 @@ export type GetSlots = {
|
|||
frequency: number;
|
||||
workingHours: WorkingHours[];
|
||||
minimumBookingNotice: number;
|
||||
eventLength: number;
|
||||
};
|
||||
export type WorkingHoursTimeFrame = { startTime: number; endTime: number };
|
||||
|
||||
const splitAvailableTime = (
|
||||
startTimeMinutes: number,
|
||||
endTimeMinutes: number,
|
||||
frequency: number
|
||||
frequency: number,
|
||||
eventLength: number
|
||||
): Array<WorkingHoursTimeFrame> => {
|
||||
let initialTime = startTimeMinutes;
|
||||
const finalizationTime = endTimeMinutes;
|
||||
const result = [] as Array<WorkingHoursTimeFrame>;
|
||||
while (initialTime < finalizationTime) {
|
||||
const periodTime = initialTime + frequency;
|
||||
result.push({ startTime: initialTime, endTime: periodTime });
|
||||
const slotEndTime = initialTime + eventLength;
|
||||
/*
|
||||
check if the slot end time surpasses availability end time of the user
|
||||
1 minute is added to round up the hour mark so that end of the slot is considered in the check instead of x9
|
||||
eg: if finalization time is 11:59, slotEndTime is 12:00, we ideally want the slot to be available
|
||||
*/
|
||||
if (slotEndTime <= finalizationTime + 1) result.push({ startTime: initialTime, endTime: periodTime });
|
||||
initialTime += frequency;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const getSlots = ({ inviteeDate, frequency, minimumBookingNotice, workingHours }: GetSlots) => {
|
||||
const getSlots = ({ inviteeDate, frequency, minimumBookingNotice, workingHours, eventLength }: GetSlots) => {
|
||||
// current date in invitee tz
|
||||
const startDate = dayjs().add(minimumBookingNotice, "minute");
|
||||
const startOfDay = dayjs.utc().startOf("day");
|
||||
|
@ -59,7 +67,7 @@ const getSlots = ({ inviteeDate, frequency, minimumBookingNotice, workingHours }
|
|||
|
||||
// Here we split working hour in chunks for every frequency available that can fit in whole working hour
|
||||
localWorkingHours.forEach((item, index) => {
|
||||
slotsTimeFrameAvailable.push(...splitAvailableTime(item.startTime, item.endTime, frequency));
|
||||
slotsTimeFrameAvailable.push(...splitAvailableTime(item.startTime, item.endTime, frequency, eventLength));
|
||||
});
|
||||
|
||||
slotsTimeFrameAvailable.forEach((item) => {
|
||||
|
|
|
@ -5,6 +5,7 @@ import utc from "dayjs/plugin/utc";
|
|||
import MockDate from "mockdate";
|
||||
|
||||
import { MINUTES_DAY_END, MINUTES_DAY_START } from "@lib/availability";
|
||||
import { getFilteredTimes } from "@lib/hooks/useSlots";
|
||||
import getSlots from "@lib/slots";
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
@ -26,6 +27,7 @@ it("can fit 24 hourly slots for an empty day", async () => {
|
|||
endTime: MINUTES_DAY_END,
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
})
|
||||
).toHaveLength(24);
|
||||
});
|
||||
|
@ -45,6 +47,7 @@ it("only shows future booking slots on the same day", async () => {
|
|||
endTime: MINUTES_DAY_END,
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
})
|
||||
).toHaveLength(12);
|
||||
});
|
||||
|
@ -62,6 +65,7 @@ it("can cut off dates that due to invitee timezone differences fall on the next
|
|||
endTime: MINUTES_DAY_END,
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
})
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
@ -80,6 +84,7 @@ it("can cut off dates that due to invitee timezone differences fall on the previ
|
|||
frequency: 60,
|
||||
minimumBookingNotice: 0,
|
||||
workingHours,
|
||||
eventLength: 60,
|
||||
})
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
@ -98,6 +103,65 @@ it("adds minimum booking notice correctly", async () => {
|
|||
endTime: MINUTES_DAY_END,
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
})
|
||||
).toHaveLength(11);
|
||||
});
|
||||
|
||||
it("adds buffer time", async () => {
|
||||
expect(
|
||||
getFilteredTimes({
|
||||
times: getSlots({
|
||||
inviteeDate: dayjs.utc().add(1, "day"),
|
||||
frequency: 60,
|
||||
minimumBookingNotice: 0,
|
||||
workingHours: [
|
||||
{
|
||||
days: Array.from(Array(7).keys()),
|
||||
startTime: MINUTES_DAY_START,
|
||||
endTime: MINUTES_DAY_END,
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
}),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2021-06-21 12:50:00", "YYYY-MM-DD HH:mm:ss").toDate(),
|
||||
end: dayjs.utc("2021-06-21 13:50:00", "YYYY-MM-DD HH:mm:ss").toDate(),
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
beforeBufferTime: 15,
|
||||
afterBufferTime: 15,
|
||||
})
|
||||
).toHaveLength(20);
|
||||
});
|
||||
|
||||
it("adds buffer time with custom slot interval", async () => {
|
||||
expect(
|
||||
getFilteredTimes({
|
||||
times: getSlots({
|
||||
inviteeDate: dayjs.utc().add(1, "day"),
|
||||
frequency: 5,
|
||||
minimumBookingNotice: 0,
|
||||
workingHours: [
|
||||
{
|
||||
days: Array.from(Array(7).keys()),
|
||||
startTime: MINUTES_DAY_START,
|
||||
endTime: MINUTES_DAY_END,
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
}),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2021-06-21 12:50:00", "YYYY-MM-DD HH:mm:ss").toDate(),
|
||||
end: dayjs.utc("2021-06-21 13:50:00", "YYYY-MM-DD HH:mm:ss").toDate(),
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
beforeBufferTime: 15,
|
||||
afterBufferTime: 15,
|
||||
})
|
||||
).toHaveLength(239);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue