fix: better slot starting times

## What does this PR do?

Currently, we start the first slot always at the nearest 15 minutes. This is not ideal as for some duration other slot starting time make more sense. So with this PR the starting times are defined as follow: 

- Frequency is exact hours (60, 120, 180, ...), slot start time is a full hour
- Frequency is half hours (30, 90, ...), slot start time is half or full hours (8:00, 8:30, ...)
- Same with 20-minute events (20, 40, ...) and 10-minute events 
- Everything else will start at the nearest 15 min slot

It also fixes that slot times are shifted when there is a busy slot with a different duration. Here is a before and after of a 30-min event with a 5-minute busy slot at 1:00 pm

Before: 
![Screenshot 2023-07-07 at 13 31 45](https://github.com/calcom/cal.com/assets/30310907/b92d4ff4-49f1-48f4-a973-99266f61d919)

After
![Screenshot 2023-07-07 at 13 34 01](https://github.com/calcom/cal.com/assets/30310907/042c7ef7-8c2a-4cd9-b663-183bc07b5864)


#### 30 Minute events, availability starting at 7:15

Before: 
![Screenshot 2023-07-06 at 12 40 00](https://github.com/calcom/cal.com/assets/30310907/752ed978-83cf-4ee9-a38d-b5795df6daec)

After:
![Screenshot 2023-07-06 at 12 40 42](https://github.com/calcom/cal.com/assets/30310907/5d51ec15-5be8-4f3b-b374-46dad35216b8)

## Type of change
- Bug fix (non-breaking change which fixes an issue)

## How should this be tested?

- Check if slot times are shown as described
- Test with different intervals/durations
- Test with busy times
- Test with different availabilities 

## Mandatory Tasks

- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
pull/10050/head
Carina Wollendorfer 2023-07-10 18:32:26 -04:00 committed by GitHub
parent cf57be2aef
commit 2f6b5ced04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 8 deletions

View File

@ -1028,7 +1028,7 @@ describe("getSchedule", () => {
[
//`04:00:00.000Z`, - Blocked with User 101
`04:15:00.000Z`,
//`05:30:00.000Z`, - Blocked with User 102 in event 2
//`05:00:00.000Z`, - Blocked with User 102 in event 2
`05:45:00.000Z`,
`06:30:00.000Z`,
`07:15:00.000Z`,

View File

@ -160,19 +160,47 @@ function buildSlotsWithDateRanges({
frequency = minimumOfOne(frequency);
eventLength = minimumOfOne(eventLength);
offsetStart = offsetStart ? minimumOfOne(offsetStart) : 0;
const slots: { time: Dayjs; userIds?: number[] }[] = [];
dateRanges.forEach((range) => {
const startTimeWithMinNotice = dayjs.utc().add(minimumBookingNotice, "minute");
let slotStartTime = range.start.isAfter(startTimeWithMinNotice) ? range.start : startTimeWithMinNotice;
slotStartTime =
slotStartTime.utc().minute() % 15 !== 0
? slotStartTime
.startOf("day")
.add(slotStartTime.hour() * 60 + Math.ceil(slotStartTime.minute() / 15) * 15, "minute")
: slotStartTime;
let previousStartTime;
// check if we we already have slots on that day (in organizer's timezone)
if (
slots.length &&
dayjs
.utc(range.start)
.add(range.start.utcOffset())
.isSame(dayjs.utc(slots[slots.length - 1].time).add(slots[slots.length - 1].time.utcOffset()), "day")
) {
previousStartTime = slots[slots.length - 1].time;
}
if (!previousStartTime) {
let interval = 15;
const intervalsWithDefinedStartTimes = [60, 30, 20, 10];
for (let i = 0; i < intervalsWithDefinedStartTimes.length; i++) {
if (frequency % intervalsWithDefinedStartTimes[i] === 0) {
interval = intervalsWithDefinedStartTimes[i];
break;
}
}
slotStartTime =
slotStartTime.utc().minute() % interval !== 0
? slotStartTime
.startOf("hour")
.add(Math.ceil(slotStartTime.minute() / interval) * interval, "minute")
: slotStartTime;
} else {
const minuteOffset =
Math.ceil(slotStartTime.diff(previousStartTime, "minutes") / frequency) * frequency;
slotStartTime = previousStartTime.add(minuteOffset, "minutes");
}
// Adding 1 minute to date ranges that end at midnight to ensure that the last slot is included
const rangeEnd = range.end