Set buffer time before/after event type (#2015)

* before and after buffer added to handleAvailableSlots function

* --WIP

* added migration

* pull buffer data from DB

* cleanup

* added buffer input in form

* removed unused functions in controller field

* improved the buffer time check

* fixed default value and added preceding event afterbuffer consideration

* fixed e2e test issue

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Syed Ali Shahbaz 2022-03-04 15:49:03 +05:30 committed by GitHub
parent b77923fc65
commit eeb0cd7e4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 164 additions and 2 deletions

View file

@ -14,6 +14,8 @@ import Loader from "@components/Loader";
type AvailableTimesProps = { type AvailableTimesProps = {
timeFormat: string; timeFormat: string;
minimumBookingNotice: number; minimumBookingNotice: number;
beforeBufferTime: number;
afterBufferTime: number;
eventTypeId: number; eventTypeId: number;
eventLength: number; eventLength: number;
slotInterval: number | null; slotInterval: number | null;
@ -33,6 +35,8 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
timeFormat, timeFormat,
users, users,
schedulingType, schedulingType,
beforeBufferTime,
afterBufferTime,
}) => { }) => {
const { t, i18n } = useLocale(); const { t, i18n } = useLocale();
const router = useRouter(); const router = useRouter();
@ -45,6 +49,8 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
schedulingType, schedulingType,
users, users,
minimumBookingNotice, minimumBookingNotice,
beforeBufferTime,
afterBufferTime,
eventTypeId, eventTypeId,
}); });

View file

@ -241,6 +241,8 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
date={selectedDate} date={selectedDate}
users={eventType.users} users={eventType.users}
schedulingType={eventType.schedulingType ?? null} schedulingType={eventType.schedulingType ?? null}
beforeBufferTime={eventType.beforeEventBuffer}
afterBufferTime={eventType.afterEventBuffer}
/> />
)} )}
</div> </div>

View file

@ -30,10 +30,21 @@ type UseSlotsProps = {
date: Dayjs; date: Dayjs;
users: { username: string | null }[]; users: { username: string | null }[];
schedulingType: SchedulingType | null; schedulingType: SchedulingType | null;
beforeBufferTime?: number;
afterBufferTime?: number;
}; };
export const useSlots = (props: UseSlotsProps) => { export const useSlots = (props: UseSlotsProps) => {
const { slotInterval, eventLength, minimumBookingNotice = 0, date, users, eventTypeId } = props; const {
slotInterval,
eventLength,
minimumBookingNotice = 0,
beforeBufferTime = 0,
afterBufferTime = 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);
@ -124,6 +135,29 @@ export const useSlots = (props: UseSlotsProps) => {
// Check if startTime is between slot // Check if startTime is between slot
else if (startTime.isBetween(times[i], times[i].add(eventLength, "minutes"))) { else if (startTime.isBetween(times[i], times[i].add(eventLength, "minutes"))) {
times.splice(i, 1); 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 { } else {
return true; return true;
} }

View file

@ -14,6 +14,8 @@ export type AdvancedOptions = {
requiresConfirmation?: boolean; requiresConfirmation?: boolean;
disableGuests?: boolean; disableGuests?: boolean;
minimumBookingNotice?: number; minimumBookingNotice?: number;
beforeBufferTime?: number;
afterBufferTime?: number;
slotInterval?: number | null; slotInterval?: number | null;
price?: number; price?: number;
currency?: string; currency?: string;

View file

@ -44,6 +44,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
periodCountCalendarDays: true, periodCountCalendarDays: true,
schedulingType: true, schedulingType: true,
minimumBookingNotice: true, minimumBookingNotice: true,
beforeEventBuffer: true,
afterEventBuffer: true,
timeZone: true, timeZone: true,
metadata: true, metadata: true,
slotInterval: true, slotInterval: true,

View file

@ -364,6 +364,8 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
periodCountCalendarDays: "1" | "0"; periodCountCalendarDays: "1" | "0";
periodDates: { startDate: Date; endDate: Date }; periodDates: { startDate: Date; endDate: Date };
minimumBookingNotice: number; minimumBookingNotice: number;
beforeBufferTime: number;
afterBufferTime: number;
slotInterval: number | null; slotInterval: number | null;
destinationCalendar: { destinationCalendar: {
integration: string; integration: string;
@ -683,7 +685,14 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
<Form <Form
form={formMethods} form={formMethods}
handleSubmit={async (values) => { handleSubmit={async (values) => {
const { periodDates, periodCountCalendarDays, smartContractAddress, ...input } = values; const {
periodDates,
periodCountCalendarDays,
smartContractAddress,
beforeBufferTime,
afterBufferTime,
...input
} = values;
updateMutation.mutate({ updateMutation.mutate({
...input, ...input,
availability: availabilityState, availability: availabilityState,
@ -691,6 +700,8 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
periodEndDate: periodDates.endDate, periodEndDate: periodDates.endDate,
periodCountCalendarDays: periodCountCalendarDays === "1", periodCountCalendarDays: periodCountCalendarDays === "1",
id: eventType.id, id: eventType.id,
beforeEventBuffer: beforeBufferTime,
afterEventBuffer: afterBufferTime,
metadata: smartContractAddress metadata: smartContractAddress
? { ? {
smartContractAddress, smartContractAddress,
@ -1189,6 +1200,98 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
/> />
</div> </div>
</div> </div>
<hr className="border-neutral-200" />
<div className="block sm:flex">
<div className="min-w-48 mb-4 sm:mb-0">
<label
htmlFor="bufferTime"
className="mt-2.5 flex text-sm font-medium text-neutral-700">
{t("buffer_time")}
</label>
</div>
<div className="w-full">
<div className="inline-flex w-full space-x-2">
<div className="w-full">
<label
htmlFor="beforeBufferTime"
className="mb-2 flex text-sm font-medium text-neutral-700">
{t("before_event")}
</label>
<Controller
name="beforeBufferTime"
control={formMethods.control}
defaultValue={eventType.beforeEventBuffer || 0}
render={({ field: { onChange, value } }) => {
const beforeBufferOptions = [
{
label: t("event_buffer_default"),
value: 0,
},
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
];
return (
<Select
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
onChange={(val) => {
if (val) onChange(val.value);
}}
defaultValue={
beforeBufferOptions.find((option) => option.value === value) ||
beforeBufferOptions[0]
}
options={beforeBufferOptions}
/>
);
}}
/>
</div>
<div className="w-full">
<label
htmlFor="afterBufferTime"
className="mb-2 flex text-sm font-medium text-neutral-700">
{t("after_event")}
</label>
<Controller
name="afterBufferTime"
control={formMethods.control}
defaultValue={eventType.afterEventBuffer || 0}
render={({ field: { onChange, value } }) => {
const afterBufferOptions = [
{
label: t("event_buffer_default"),
value: 0,
},
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
];
return (
<Select
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
onChange={(val) => {
if (val) onChange(val.value);
}}
defaultValue={
afterBufferOptions.find((option) => option.value === value) ||
afterBufferOptions[0]
}
options={afterBufferOptions}
/>
);
}}
/>
</div>
</div>
</div>
</div>
<hr className="border-neutral-200" /> <hr className="border-neutral-200" />
<div className="block sm:flex"> <div className="block sm:flex">
@ -1618,6 +1721,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
requiresConfirmation: true, requiresConfirmation: true,
disableGuests: true, disableGuests: true,
minimumBookingNotice: true, minimumBookingNotice: true,
beforeEventBuffer: true,
afterEventBuffer: true,
slotInterval: true, slotInterval: true,
team: { team: {
select: { select: {

View file

@ -61,6 +61,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
periodDays: true, periodDays: true,
periodCountCalendarDays: true, periodCountCalendarDays: true,
minimumBookingNotice: true, minimumBookingNotice: true,
beforeEventBuffer: true,
afterEventBuffer: true,
price: true, price: true,
currency: true, currency: true,
timeZone: true, timeZone: true,

View file

@ -284,6 +284,10 @@
"hover_over_bold_times_tip": "Tip: Hover over the bold times for a full timestamp", "hover_over_bold_times_tip": "Tip: Hover over the bold times for a full timestamp",
"start_time": "Start time", "start_time": "Start time",
"end_time": "End time", "end_time": "End time",
"buffer_time": "Buffer time",
"before_event": "Before event",
"after_event": "After event",
"event_buffer_default": "No buffer time",
"buffer": "Buffer", "buffer": "Buffer",
"your_day_starts_at": "Your day starts at", "your_day_starts_at": "Your day starts at",
"your_day_ends_at": "Your day ends at", "your_day_ends_at": "Your day ends at",

View file

@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "afterEventBuffer" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "beforeEventBuffer" INTEGER NOT NULL DEFAULT 0;

View file

@ -59,6 +59,8 @@ model EventType {
requiresConfirmation Boolean @default(false) requiresConfirmation Boolean @default(false)
disableGuests Boolean @default(false) disableGuests Boolean @default(false)
minimumBookingNotice Int @default(120) minimumBookingNotice Int @default(120)
beforeEventBuffer Int @default(0)
afterEventBuffer Int @default(0)
schedulingType SchedulingType? schedulingType SchedulingType?
Schedule Schedule[] Schedule Schedule[]
price Int @default(0) price Int @default(0)