Merge pull request #384 from emrysal/feature/minimum-booking-notice
Feature/minimum booking notice
This commit is contained in:
commit
3c55660537
8 changed files with 52 additions and 15 deletions
|
@ -7,6 +7,7 @@ const AvailableTimes = ({
|
||||||
date,
|
date,
|
||||||
eventLength,
|
eventLength,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
|
minimumBookingNotice,
|
||||||
workingHours,
|
workingHours,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
user,
|
user,
|
||||||
|
@ -20,6 +21,7 @@ const AvailableTimes = ({
|
||||||
eventLength,
|
eventLength,
|
||||||
workingHours,
|
workingHours,
|
||||||
organizerTimeZone,
|
organizerTimeZone,
|
||||||
|
minimumBookingNotice,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -23,6 +23,7 @@ const DatePicker = ({
|
||||||
periodEndDate,
|
periodEndDate,
|
||||||
periodDays,
|
periodDays,
|
||||||
periodCountCalendarDays,
|
periodCountCalendarDays,
|
||||||
|
minimumBookingNotice,
|
||||||
}) => {
|
}) => {
|
||||||
const [calendar, setCalendar] = useState([]);
|
const [calendar, setCalendar] = useState([]);
|
||||||
const [selectedMonth, setSelectedMonth] = useState<number>();
|
const [selectedMonth, setSelectedMonth] = useState<number>();
|
||||||
|
@ -77,6 +78,7 @@ const DatePicker = ({
|
||||||
!getSlots({
|
!getSlots({
|
||||||
inviteeDate: date,
|
inviteeDate: date,
|
||||||
frequency: eventLength,
|
frequency: eventLength,
|
||||||
|
minimumBookingNotice,
|
||||||
workingHours,
|
workingHours,
|
||||||
organizerTimeZone,
|
organizerTimeZone,
|
||||||
}).length
|
}).length
|
||||||
|
@ -93,6 +95,7 @@ const DatePicker = ({
|
||||||
!getSlots({
|
!getSlots({
|
||||||
inviteeDate: date,
|
inviteeDate: date,
|
||||||
frequency: eventLength,
|
frequency: eventLength,
|
||||||
|
minimumBookingNotice,
|
||||||
workingHours,
|
workingHours,
|
||||||
organizerTimeZone,
|
organizerTimeZone,
|
||||||
}).length
|
}).length
|
||||||
|
@ -106,6 +109,7 @@ const DatePicker = ({
|
||||||
!getSlots({
|
!getSlots({
|
||||||
inviteeDate: date,
|
inviteeDate: date,
|
||||||
frequency: eventLength,
|
frequency: eventLength,
|
||||||
|
minimumBookingNotice,
|
||||||
workingHours,
|
workingHours,
|
||||||
organizerTimeZone,
|
organizerTimeZone,
|
||||||
}).length
|
}).length
|
||||||
|
|
17
lib/slots.ts
17
lib/slots.ts
|
@ -1,7 +1,6 @@
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import timezone from "dayjs/plugin/timezone";
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
|
@ -119,10 +118,20 @@ const getSlots = ({
|
||||||
workingHours,
|
workingHours,
|
||||||
organizerTimeZone,
|
organizerTimeZone,
|
||||||
}: GetSlots): Dayjs[] => {
|
}: GetSlots): Dayjs[] => {
|
||||||
const startTime = dayjs().utcOffset(inviteeDate.utcOffset()).isSame(inviteeDate, "day")
|
// current date in invitee tz
|
||||||
? inviteeDate.hour() * 60 + inviteeDate.minute() + (minimumBookingNotice || 0)
|
const currentDate = dayjs().utcOffset(inviteeDate.utcOffset());
|
||||||
: 0;
|
const startDate = currentDate.add(minimumBookingNotice, "minutes"); // + minimum notice period
|
||||||
|
// when the invitee date is not the same as the current date, reset the date to the start of day
|
||||||
|
if (inviteeDate.date() !== currentDate.date()) {
|
||||||
|
inviteeDate = inviteeDate.startOf("day");
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = startDate.isAfter(inviteeDate)
|
||||||
|
? // block out everything when inviteeDate is less than startDate
|
||||||
|
startDate.date() > inviteeDate.date()
|
||||||
|
? 1440
|
||||||
|
: startDate.hour() * 60 + startDate.minute()
|
||||||
|
: 0;
|
||||||
const inviteeBounds = inviteeBoundary(startTime, inviteeDate.utcOffset(), frequency);
|
const inviteeBounds = inviteeBoundary(startTime, inviteeDate.utcOffset(), frequency);
|
||||||
|
|
||||||
return getOverlaps(
|
return getOverlaps(
|
||||||
|
|
|
@ -167,14 +167,16 @@ export default function Type(props): Type {
|
||||||
organizerTimeZone={props.eventType.timeZone || props.user.timeZone}
|
organizerTimeZone={props.eventType.timeZone || props.user.timeZone}
|
||||||
inviteeTimeZone={timeZone()}
|
inviteeTimeZone={timeZone()}
|
||||||
eventLength={props.eventType.length}
|
eventLength={props.eventType.length}
|
||||||
|
minimumBookingNotice={props.eventType.minimumBookingNotice}
|
||||||
/>
|
/>
|
||||||
{selectedDate && (
|
{selectedDate && (
|
||||||
<AvailableTimes
|
<AvailableTimes
|
||||||
workingHours={props.workingHours}
|
workingHours={props.workingHours}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
organizerTimeZone={props.eventType.timeZone || props.user.timeZone}
|
organizerTimeZone={props.eventType.timeZone || props.user.timeZone}
|
||||||
eventLength={props.eventType.length}
|
minimumBookingNotice={props.eventType.minimumBookingNotice}
|
||||||
eventTypeId={props.eventType.id}
|
eventTypeId={props.eventType.id}
|
||||||
|
eventLength={props.eventType.length}
|
||||||
date={selectedDate}
|
date={selectedDate}
|
||||||
user={props.user}
|
user={props.user}
|
||||||
/>
|
/>
|
||||||
|
@ -238,6 +240,7 @@ export const getServerSideProps: GetServerSideProps = async (context: GetServerS
|
||||||
"periodStartDate",
|
"periodStartDate",
|
||||||
"periodEndDate",
|
"periodEndDate",
|
||||||
"periodCountCalendarDays",
|
"periodCountCalendarDays",
|
||||||
|
"minimumBookingNotice",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
periodStartDate: req.body.periodStartDate,
|
periodStartDate: req.body.periodStartDate,
|
||||||
periodEndDate: req.body.periodEndDate,
|
periodEndDate: req.body.periodEndDate,
|
||||||
periodCountCalendarDays: req.body.periodCountCalendarDays,
|
periodCountCalendarDays: req.body.periodCountCalendarDays,
|
||||||
|
minimumBookingNotice: req.body.minimumBookingNotice,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (req.method == "POST") {
|
if (req.method == "POST") {
|
||||||
|
|
|
@ -66,7 +66,8 @@ type EventTypeInput = {
|
||||||
periodStartDate?: Date | string;
|
periodStartDate?: Date | string;
|
||||||
periodEndDate?: Date | string;
|
periodEndDate?: Date | string;
|
||||||
periodCountCalendarDays?: boolean;
|
periodCountCalendarDays?: boolean;
|
||||||
enteredRequiresConfirmation: boolean;
|
requiresConfirmation: boolean;
|
||||||
|
minimumBookingNotice: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PERIOD_TYPES = [
|
const PERIOD_TYPES = [
|
||||||
|
@ -92,7 +93,6 @@ export default function EventTypePage({
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
console.log(eventType);
|
|
||||||
const inputOptions: OptionBase[] = [
|
const inputOptions: OptionBase[] = [
|
||||||
{ value: EventTypeCustomInputType.Text, label: "Text" },
|
{ value: EventTypeCustomInputType.Text, label: "Text" },
|
||||||
{ value: EventTypeCustomInputType.TextLong, label: "Multiline Text" },
|
{ value: EventTypeCustomInputType.TextLong, label: "Multiline Text" },
|
||||||
|
@ -174,6 +174,7 @@ export default function EventTypePage({
|
||||||
const lengthRef = useRef<HTMLInputElement>();
|
const lengthRef = useRef<HTMLInputElement>();
|
||||||
const isHiddenRef = useRef<HTMLInputElement>();
|
const isHiddenRef = useRef<HTMLInputElement>();
|
||||||
const requiresConfirmationRef = useRef<HTMLInputElement>();
|
const requiresConfirmationRef = useRef<HTMLInputElement>();
|
||||||
|
const minimumBookingNoticeRef = useRef<HTMLInputElement>();
|
||||||
const eventNameRef = useRef<HTMLInputElement>();
|
const eventNameRef = useRef<HTMLInputElement>();
|
||||||
const periodDaysRef = useRef<HTMLInputElement>();
|
const periodDaysRef = useRef<HTMLInputElement>();
|
||||||
const periodDaysTypeRef = useRef<HTMLSelectElement>();
|
const periodDaysTypeRef = useRef<HTMLSelectElement>();
|
||||||
|
@ -190,6 +191,7 @@ export default function EventTypePage({
|
||||||
const enteredDescription: string = descriptionRef.current.value;
|
const enteredDescription: string = descriptionRef.current.value;
|
||||||
const enteredLength: number = parseInt(lengthRef.current.value);
|
const enteredLength: number = parseInt(lengthRef.current.value);
|
||||||
const enteredIsHidden: boolean = isHiddenRef.current.checked;
|
const enteredIsHidden: boolean = isHiddenRef.current.checked;
|
||||||
|
const enteredMinimumBookingNotice: number = parseInt(minimumBookingNoticeRef.current.value);
|
||||||
const enteredRequiresConfirmation: boolean = requiresConfirmationRef.current.checked;
|
const enteredRequiresConfirmation: boolean = requiresConfirmationRef.current.checked;
|
||||||
const enteredEventName: string = eventNameRef.current.value;
|
const enteredEventName: string = eventNameRef.current.value;
|
||||||
|
|
||||||
|
@ -200,14 +202,6 @@ export default function EventTypePage({
|
||||||
const enteredPeriodStartDate = periodStartDate ? periodStartDate.toDate() : null;
|
const enteredPeriodStartDate = periodStartDate ? periodStartDate.toDate() : null;
|
||||||
const enteredPeriodEndDate = periodEndDate ? periodEndDate.toDate() : null;
|
const enteredPeriodEndDate = periodEndDate ? periodEndDate.toDate() : null;
|
||||||
|
|
||||||
console.log("values", {
|
|
||||||
type,
|
|
||||||
periodDaysTypeRef,
|
|
||||||
enteredPeriodDays,
|
|
||||||
enteredPeriodDaysType,
|
|
||||||
enteredPeriodStartDate,
|
|
||||||
enteredPeriodEndDate,
|
|
||||||
});
|
|
||||||
// TODO: Add validation
|
// TODO: Add validation
|
||||||
|
|
||||||
const payload: EventTypeInput = {
|
const payload: EventTypeInput = {
|
||||||
|
@ -226,6 +220,7 @@ export default function EventTypePage({
|
||||||
periodStartDate: enteredPeriodStartDate,
|
periodStartDate: enteredPeriodStartDate,
|
||||||
periodEndDate: enteredPeriodEndDate,
|
periodEndDate: enteredPeriodEndDate,
|
||||||
periodCountCalendarDays: enteredPeriodDaysType,
|
periodCountCalendarDays: enteredPeriodDaysType,
|
||||||
|
minimumBookingNotice: enteredMinimumBookingNotice,
|
||||||
requiresConfirmation: enteredRequiresConfirmation,
|
requiresConfirmation: enteredRequiresConfirmation,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -671,6 +666,25 @@ export default function EventTypePage({
|
||||||
|
|
||||||
<fieldset className="my-8">
|
<fieldset className="my-8">
|
||||||
<Text variant="largetitle">When can people book this event?</Text>
|
<Text variant="largetitle">When can people book this event?</Text>
|
||||||
|
<div className="my-4">
|
||||||
|
<label htmlFor="minimumAdvance" className="block text-sm font-medium text-gray-700">
|
||||||
|
Minimum booking notice
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 relative rounded-md shadow-sm">
|
||||||
|
<input
|
||||||
|
ref={minimumBookingNoticeRef}
|
||||||
|
type="number"
|
||||||
|
name="minimumAdvance"
|
||||||
|
id="minimumAdvance"
|
||||||
|
required
|
||||||
|
className="focus:ring-blue-500 focus:border-blue-500 block w-full pr-20 sm:text-sm border-gray-300 rounded-md"
|
||||||
|
defaultValue={eventType.minimumBookingNotice}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm">
|
||||||
|
minutes
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<hr className="my-8" />
|
<hr className="my-8" />
|
||||||
<section className="space-y-12">
|
<section className="space-y-12">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
|
@ -1019,6 +1033,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, query
|
||||||
periodEndDate: true,
|
periodEndDate: true,
|
||||||
periodCountCalendarDays: true,
|
periodCountCalendarDays: true,
|
||||||
requiresConfirmation: true,
|
requiresConfirmation: true,
|
||||||
|
minimumBookingNotice: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "EventType" ADD COLUMN "minimumBookingNotice" INTEGER NOT NULL DEFAULT 120;
|
|
@ -31,6 +31,7 @@ model EventType {
|
||||||
periodDays Int?
|
periodDays Int?
|
||||||
periodCountCalendarDays Boolean?
|
periodCountCalendarDays Boolean?
|
||||||
requiresConfirmation Boolean @default(false)
|
requiresConfirmation Boolean @default(false)
|
||||||
|
minimumBookingNotice Int @default(120)
|
||||||
}
|
}
|
||||||
|
|
||||||
model Credential {
|
model Credential {
|
||||||
|
|
Loading…
Reference in a new issue