From 2f6b5ced04dcf6386852840837863dbad5419d80 Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Mon, 10 Jul 2023 18:32:26 -0400 Subject: [PATCH] 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. --- apps/web/test/lib/getSchedule.test.ts | 2 +- packages/lib/slots.ts | 42 ++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/web/test/lib/getSchedule.test.ts b/apps/web/test/lib/getSchedule.test.ts index 44e2231526..9c41b0682e 100644 --- a/apps/web/test/lib/getSchedule.test.ts +++ b/apps/web/test/lib/getSchedule.test.ts @@ -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`, diff --git a/packages/lib/slots.ts b/packages/lib/slots.ts index a4ba2c5cf2..4f0a131b93 100644 --- a/packages/lib/slots.ts +++ b/packages/lib/slots.ts @@ -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