Allows setting the event frequency to other than event length (#1349)
This commit is contained in:
parent
38f762f7b2
commit
cbf528c33e
12 changed files with 70 additions and 4 deletions
|
@ -16,6 +16,7 @@ type AvailableTimesProps = {
|
||||||
minimumBookingNotice: number;
|
minimumBookingNotice: number;
|
||||||
eventTypeId: number;
|
eventTypeId: number;
|
||||||
eventLength: number;
|
eventLength: number;
|
||||||
|
slotInterval: number | null;
|
||||||
date: Dayjs;
|
date: Dayjs;
|
||||||
users: {
|
users: {
|
||||||
username: string | null;
|
username: string | null;
|
||||||
|
@ -27,6 +28,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||||
date,
|
date,
|
||||||
eventLength,
|
eventLength,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
|
slotInterval,
|
||||||
minimumBookingNotice,
|
minimumBookingNotice,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
users,
|
users,
|
||||||
|
@ -38,6 +40,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||||
|
|
||||||
const { slots, loading, error } = useSlots({
|
const { slots, loading, error } = useSlots({
|
||||||
date,
|
date,
|
||||||
|
slotInterval,
|
||||||
eventLength,
|
eventLength,
|
||||||
schedulingType,
|
schedulingType,
|
||||||
users,
|
users,
|
||||||
|
|
|
@ -220,6 +220,7 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
minimumBookingNotice={eventType.minimumBookingNotice}
|
minimumBookingNotice={eventType.minimumBookingNotice}
|
||||||
eventTypeId={eventType.id}
|
eventTypeId={eventType.id}
|
||||||
|
slotInterval={eventType.slotInterval}
|
||||||
eventLength={eventType.length}
|
eventLength={eventType.length}
|
||||||
date={selectedDate}
|
date={selectedDate}
|
||||||
users={eventType.users}
|
users={eventType.users}
|
||||||
|
|
|
@ -23,6 +23,7 @@ type Slot = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type UseSlotsProps = {
|
type UseSlotsProps = {
|
||||||
|
slotInterval: number | null;
|
||||||
eventLength: number;
|
eventLength: number;
|
||||||
eventTypeId: number;
|
eventTypeId: number;
|
||||||
minimumBookingNotice?: number;
|
minimumBookingNotice?: number;
|
||||||
|
@ -32,7 +33,7 @@ type UseSlotsProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSlots = (props: UseSlotsProps) => {
|
export const useSlots = (props: UseSlotsProps) => {
|
||||||
const { eventLength, minimumBookingNotice = 0, date, users, eventTypeId } = props;
|
const { slotInterval, eventLength, minimumBookingNotice = 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);
|
||||||
|
@ -102,7 +103,7 @@ export const useSlots = (props: UseSlotsProps) => {
|
||||||
const handleAvailableSlots = async (res: Response) => {
|
const handleAvailableSlots = async (res: Response) => {
|
||||||
const responseBody: AvailabilityUserResponse = await res.json();
|
const responseBody: AvailabilityUserResponse = await res.json();
|
||||||
const times = getSlots({
|
const times = getSlots({
|
||||||
frequency: eventLength,
|
frequency: slotInterval || eventLength,
|
||||||
inviteeDate: date,
|
inviteeDate: date,
|
||||||
workingHours: responseBody.workingHours,
|
workingHours: responseBody.workingHours,
|
||||||
minimumBookingNotice,
|
minimumBookingNotice,
|
||||||
|
|
|
@ -12,6 +12,7 @@ export type AdvancedOptions = {
|
||||||
requiresConfirmation?: boolean;
|
requiresConfirmation?: boolean;
|
||||||
disableGuests?: boolean;
|
disableGuests?: boolean;
|
||||||
minimumBookingNotice?: number;
|
minimumBookingNotice?: number;
|
||||||
|
slotInterval?: number | null;
|
||||||
price?: number;
|
price?: number;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
schedulingType?: SchedulingType;
|
schedulingType?: SchedulingType;
|
||||||
|
|
|
@ -44,6 +44,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
schedulingType: true,
|
schedulingType: true,
|
||||||
minimumBookingNotice: true,
|
minimumBookingNotice: true,
|
||||||
timeZone: true,
|
timeZone: true,
|
||||||
|
slotInterval: true,
|
||||||
users: {
|
users: {
|
||||||
select: {
|
select: {
|
||||||
avatar: true,
|
avatar: true,
|
||||||
|
|
|
@ -132,6 +132,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
req.body.minimumBookingNotice || req.body.minimumBookingNotice === 0
|
req.body.minimumBookingNotice || req.body.minimumBookingNotice === 0
|
||||||
? parseInt(req.body.minimumBookingNotice, 10)
|
? parseInt(req.body.minimumBookingNotice, 10)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
slotInterval: req.body.slotInterval,
|
||||||
price: req.body.price,
|
price: req.body.price,
|
||||||
currency: req.body.currency,
|
currency: req.body.currency,
|
||||||
};
|
};
|
||||||
|
|
|
@ -275,6 +275,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
||||||
periodDaysType: string;
|
periodDaysType: string;
|
||||||
periodDates: { startDate: Date; endDate: Date };
|
periodDates: { startDate: Date; endDate: Date };
|
||||||
minimumBookingNotice: number;
|
minimumBookingNotice: number;
|
||||||
|
slotInterval: number | null;
|
||||||
}>({
|
}>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
locations: eventType.locations || [],
|
locations: eventType.locations || [],
|
||||||
|
@ -512,6 +513,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
||||||
advancedPayload.periodStartDate = values.periodDates.startDate || undefined;
|
advancedPayload.periodStartDate = values.periodDates.startDate || undefined;
|
||||||
advancedPayload.periodEndDate = values.periodDates.endDate || undefined;
|
advancedPayload.periodEndDate = values.periodDates.endDate || undefined;
|
||||||
advancedPayload.minimumBookingNotice = values.minimumBookingNotice;
|
advancedPayload.minimumBookingNotice = values.minimumBookingNotice;
|
||||||
|
advancedPayload.slotInterval = values.slotInterval;
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
advancedPayload.price =
|
advancedPayload.price =
|
||||||
!requirePayment ? undefined :
|
!requirePayment ? undefined :
|
||||||
|
@ -849,6 +851,53 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="items-center block sm:flex">
|
||||||
|
<div className="mb-4 min-w-48 sm:mb-0">
|
||||||
|
<label htmlFor="eventName" className="flex text-sm font-medium text-neutral-700">
|
||||||
|
{t("slot_interval")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="relative mt-1 rounded-sm shadow-sm">
|
||||||
|
<Controller
|
||||||
|
name="slotInterval"
|
||||||
|
control={formMethods.control}
|
||||||
|
render={() => {
|
||||||
|
const slotIntervalOptions = [
|
||||||
|
{
|
||||||
|
label: t("slot_interval_default"),
|
||||||
|
value: -1,
|
||||||
|
},
|
||||||
|
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
|
||||||
|
label: minutes + " " + t("minutes"),
|
||||||
|
value: minutes,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
isSearchable={false}
|
||||||
|
classNamePrefix="react-select"
|
||||||
|
className="flex-1 block w-full min-w-0 border border-gray-300 rounded-sm react-select-container focus:ring-primary-500 focus:border-primary-500 sm:text-sm"
|
||||||
|
onChange={(val) => {
|
||||||
|
formMethods.setValue(
|
||||||
|
"slotInterval",
|
||||||
|
val && (val.value || 0) > 0 ? val.value : null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
defaultValue={
|
||||||
|
slotIntervalOptions.find(
|
||||||
|
(option) => option.value === eventType.slotInterval
|
||||||
|
) || slotIntervalOptions[0]
|
||||||
|
}
|
||||||
|
options={slotIntervalOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="block sm:flex">
|
<div className="block sm:flex">
|
||||||
<div className="mb-4 min-w-48 sm:mb-0">
|
<div className="mb-4 min-w-48 sm:mb-0">
|
||||||
<label
|
<label
|
||||||
|
@ -1324,6 +1373,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
requiresConfirmation: true,
|
requiresConfirmation: true,
|
||||||
disableGuests: true,
|
disableGuests: true,
|
||||||
minimumBookingNotice: true,
|
minimumBookingNotice: true,
|
||||||
|
slotInterval: true,
|
||||||
team: {
|
team: {
|
||||||
select: {
|
select: {
|
||||||
slug: true,
|
slug: true,
|
||||||
|
|
|
@ -63,6 +63,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
price: true,
|
price: true,
|
||||||
currency: true,
|
currency: true,
|
||||||
timeZone: true,
|
timeZone: true,
|
||||||
|
slotInterval: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "EventType" ADD COLUMN "slotInterval" INTEGER;
|
|
@ -53,6 +53,7 @@ model EventType {
|
||||||
Schedule Schedule[]
|
Schedule Schedule[]
|
||||||
price Int @default(0)
|
price Int @default(0)
|
||||||
currency String @default("usd")
|
currency String @default("usd")
|
||||||
|
slotInterval Int?
|
||||||
|
|
||||||
@@unique([userId, slug])
|
@@unique([userId, slug])
|
||||||
}
|
}
|
||||||
|
|
|
@ -541,6 +541,8 @@
|
||||||
"new_event_type_to_book_description": "Create a new event type for people to book times with.",
|
"new_event_type_to_book_description": "Create a new event type for people to book times with.",
|
||||||
"length": "Length",
|
"length": "Length",
|
||||||
"minimum_booking_notice": "Minimum booking notice",
|
"minimum_booking_notice": "Minimum booking notice",
|
||||||
|
"slot_interval": "Time-slot intervals",
|
||||||
|
"slot_interval_default": "Use event length (default)",
|
||||||
"delete_event_type_description": "Are you sure you want to delete this event type? Anyone who you've shared this link with will no longer be able to book using it.",
|
"delete_event_type_description": "Are you sure you want to delete this event type? Anyone who you've shared this link with will no longer be able to book using it.",
|
||||||
"delete_event_type": "Delete Event Type",
|
"delete_event_type": "Delete Event Type",
|
||||||
"confirm_delete_event_type": "Yes, delete event type",
|
"confirm_delete_event_type": "Yes, delete event type",
|
||||||
|
|
|
@ -431,6 +431,8 @@
|
||||||
"add_new_event_type": "Een nieuw type afspraak toevoegen",
|
"add_new_event_type": "Een nieuw type afspraak toevoegen",
|
||||||
"new_event_type_to_book_description": "Maak een nieuw afspraak type voor bezoekers om tijden mee te reserveren.",
|
"new_event_type_to_book_description": "Maak een nieuw afspraak type voor bezoekers om tijden mee te reserveren.",
|
||||||
"length": "Lengte",
|
"length": "Lengte",
|
||||||
|
"slot_interval": "Tijdslot intervallen",
|
||||||
|
"slot_interval_default": "Gebruik evenement lengte (standaard)",
|
||||||
"minimum_booking_notice": "Minimum vooruitboeken",
|
"minimum_booking_notice": "Minimum vooruitboeken",
|
||||||
"delete_event_type_description": "Weet u zeker dat u dit evenement wilt verwijderen? Iedereen met wie u deze link heeft gedeeld zal hem niet meer kunnen gebruiken om te boeken.",
|
"delete_event_type_description": "Weet u zeker dat u dit evenement wilt verwijderen? Iedereen met wie u deze link heeft gedeeld zal hem niet meer kunnen gebruiken om te boeken.",
|
||||||
"delete_event_type": "Verwijder Evenement",
|
"delete_event_type": "Verwijder Evenement",
|
||||||
|
|
Loading…
Reference in a new issue