feat: upcoming bookings atom (#24010)
* init: upcoming bookings atom * add comments * fix: better naming * fixup * fix: use `usePrefetch` hook * add comment- for myself * chore: add changesets * fix: ignore isTeamEvent prop if we have teamId * chore: update docs * chore: implement PR feedback * refactor: remove all unnecessary props * update fetch bookings count
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@calcom/atoms": minor
|
||||
---
|
||||
|
||||
This PR adds ability to display bookings of a user event for calendar view atom
|
||||
@@ -30,7 +30,7 @@ import { CalendarView } from "@calcom/atoms";
|
||||
export default function Booker( props : BookerProps ) {
|
||||
return (
|
||||
<div>
|
||||
<CalendarView isTeamEvent={true} teamId={props.teamId} eventSlug={props.eventSlug} />
|
||||
<CalendarView teamId={props.teamId} eventSlug={props.eventSlug} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -55,7 +55,6 @@ Below is a list of props that can be passed to the create event type atom
|
||||
|
||||
| Name | Required | Description |
|
||||
|:----------------------------|:----------|:-------------------------------------------------------------------------------------------------------|
|
||||
| username | Yes | Username of the person whose schedule is to be displayed |
|
||||
| eventSlug | Yes | Unique slug created for a particular event | |
|
||||
| isTeamEvent | No | Boolean indicating if it is a team event, to be passed only for team events |
|
||||
| teamId | No | The id of the team for which the event is created, to be passed only for team events |
|
||||
| eventSlug | Yes | Unique slug created for a particular event |
|
||||
| username | Optional | Username of the person whose schedule is to be displayed, required only for individual events | | |
|
||||
| teamId | Optional | Id of the team for which the event is created, required only for team events |
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useMemo, useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
||||
import { useAvailableTimeSlots } from "@calcom/features/bookings/Booker/components/hooks/useAvailableTimeSlots";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { Calendar } from "@calcom/features/calendars/weeklyview";
|
||||
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import type { BookingStatus } from "@calcom/prisma/enums";
|
||||
|
||||
import { useOverlayCalendarStore } from "../bookings/Booker/components/OverlayCalendar/store";
|
||||
import { useBookings } from "../../platform/atoms/hooks/bookings/useBookings";
|
||||
import type { useScheduleForEventReturnType } from "../bookings/Booker/utils/event";
|
||||
import { getQueryParam } from "../bookings/Booker/utils/query-param";
|
||||
|
||||
@@ -22,12 +22,11 @@ export const LargeCalendar = ({
|
||||
schedule?: useScheduleForEventReturnType["data"];
|
||||
isLoading: boolean;
|
||||
event: {
|
||||
data?: Pick<BookerEvent, "length"> | null;
|
||||
data?: Pick<BookerEvent, "length" | "id"> | null;
|
||||
};
|
||||
}) => {
|
||||
const selectedDate = useBookerStoreContext((state) => state.selectedDate);
|
||||
const selectedEventDuration = useBookerStoreContext((state) => state.selectedDuration);
|
||||
const overlayEvents = useOverlayCalendarStore((state) => state.overlayBusyDates);
|
||||
const displayOverlay =
|
||||
getQueryParam("overlayCalendar") === "true" || localStorage?.getItem("overlayCalendarSwitchDefault");
|
||||
|
||||
@@ -40,25 +39,38 @@ export const LargeCalendar = ({
|
||||
.add(extraDays - 1, "day")
|
||||
.toDate();
|
||||
|
||||
const { data: upcomingBookings } = useBookings({
|
||||
take: 150,
|
||||
skip: 0,
|
||||
status: ["upcoming", "past", "recurring"],
|
||||
eventTypeId: event?.data?.id,
|
||||
afterStart: startDate.toISOString(),
|
||||
beforeEnd: endDate.toISOString(),
|
||||
});
|
||||
|
||||
// HACK: force rerender when overlay events change
|
||||
// Sine we dont use react router here we need to force rerender (ATOM SUPPORT)
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
|
||||
useEffect(() => {}, [displayOverlay]);
|
||||
|
||||
const overlayEventsForDate = useMemo(() => {
|
||||
if (!overlayEvents || !displayOverlay) return [];
|
||||
return overlayEvents.map((event, id) => {
|
||||
if (!upcomingBookings) return [];
|
||||
|
||||
return upcomingBookings?.map((booking) => {
|
||||
return {
|
||||
id,
|
||||
start: dayjs(event.start).toDate(),
|
||||
end: dayjs(event.end).toDate(),
|
||||
title: "Busy",
|
||||
id: booking.id,
|
||||
title: booking.title ?? `Busy`,
|
||||
start: new Date(booking.start),
|
||||
end: new Date(booking.end),
|
||||
options: {
|
||||
status: "ACCEPTED",
|
||||
borderColor: "black",
|
||||
status: booking.status.toUpperCase() as BookingStatus,
|
||||
"data-test-id": "troubleshooter-busy-event",
|
||||
className: "border-[1.5px]",
|
||||
},
|
||||
} as CalendarEvent;
|
||||
};
|
||||
});
|
||||
}, [overlayEvents, displayOverlay]);
|
||||
}, [upcomingBookings]);
|
||||
|
||||
return (
|
||||
<div className="h-full [--calendar-dates-sticky-offset:66px]">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AtomsWrapper } from "@/components/atoms-wrapper";
|
||||
import { useMemo, useEffect, useState } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -16,33 +16,34 @@ import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||
import { LargeCalendar } from "@calcom/features/calendar-view/LargeCalendar";
|
||||
import { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents";
|
||||
import { useTimesForSchedule } from "@calcom/features/schedules/lib/use-schedule/useTimesForSchedule";
|
||||
import { getRoutedTeamMemberIdsFromSearchParams } from "@calcom/lib/bookings/getRoutedTeamMemberIdsFromSearchParams";
|
||||
|
||||
import { formatUsername } from "../../booker/BookerPlatformWrapper";
|
||||
import type {
|
||||
BookerPlatformWrapperAtomPropsForIndividual,
|
||||
BookerPlatformWrapperAtomPropsForTeam,
|
||||
} from "../../booker/types";
|
||||
import { useGetBookingForReschedule } from "../../hooks/bookings/useGetBookingForReschedule";
|
||||
import { useAtomGetPublicEvent } from "../../hooks/event-types/public/useAtomGetPublicEvent";
|
||||
import { useEventType } from "../../hooks/event-types/public/useEventType";
|
||||
import { useTeamEventType } from "../../hooks/event-types/public/useTeamEventType";
|
||||
import { useAvailableSlots } from "../../hooks/useAvailableSlots";
|
||||
|
||||
const CalendarViewPlatformWrapperComponent = (
|
||||
props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam
|
||||
) => {
|
||||
const {
|
||||
eventSlug,
|
||||
isTeamEvent,
|
||||
hostsLimit,
|
||||
allowUpdatingUrlParams = false,
|
||||
teamMemberEmail,
|
||||
crmAppSlug,
|
||||
crmOwnerRecordType,
|
||||
} = props;
|
||||
type CalendarViewPlatformWrapperAtomPropsForIndividual = {
|
||||
username: string | string[];
|
||||
eventSlug: string;
|
||||
};
|
||||
|
||||
const teamId: number | undefined = props.isTeamEvent ? props.teamId : undefined;
|
||||
type CalendarViewPlatformWrapperAtomPropsForTeam = {
|
||||
teamId: number;
|
||||
eventSlug: string;
|
||||
};
|
||||
|
||||
const CalendarViewPlatformWrapperComponent = (
|
||||
props:
|
||||
| (CalendarViewPlatformWrapperAtomPropsForIndividual & { teamId?: number })
|
||||
| (CalendarViewPlatformWrapperAtomPropsForTeam & { username?: string | string[] })
|
||||
) => {
|
||||
const isTeamEvent = !!props.teamId;
|
||||
const teamId: number | undefined = props.teamId ? props.teamId : undefined;
|
||||
const username = useMemo(() => {
|
||||
if (props.username) {
|
||||
return formatUsername(props.username);
|
||||
@@ -50,16 +51,16 @@ const CalendarViewPlatformWrapperComponent = (
|
||||
return "";
|
||||
}, [props.username]);
|
||||
|
||||
const { isPending } = useEventType(username, eventSlug, isTeamEvent);
|
||||
const { isPending: isTeamPending } = useTeamEventType(teamId, eventSlug, isTeamEvent, hostsLimit);
|
||||
const { isPending } = useEventType(username, props.eventSlug, isTeamEvent);
|
||||
|
||||
const { isPending: isTeamPending } = useTeamEventType(teamId, props.eventSlug, isTeamEvent);
|
||||
|
||||
const setSelectedDuration = useBookerStoreContext((state) => state.setSelectedDuration);
|
||||
const selectedDuration = useBookerStoreContext((state) => state.selectedDuration);
|
||||
|
||||
const event = useAtomGetPublicEvent({
|
||||
username,
|
||||
eventSlug: props.eventSlug,
|
||||
isTeamEvent: props.isTeamEvent,
|
||||
isTeamEvent: isTeamEvent,
|
||||
teamId,
|
||||
selectedDuration,
|
||||
});
|
||||
@@ -90,67 +91,21 @@ const CalendarViewPlatformWrapperComponent = (
|
||||
selectedDate,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedDuration(props.duration ?? null);
|
||||
}, [props.duration, setSelectedDuration]);
|
||||
|
||||
const { timezone } = useTimePreferences();
|
||||
const isDynamic = useMemo(() => {
|
||||
return getUsernameList(username ?? "").length > 1;
|
||||
}, [username]);
|
||||
|
||||
const [routingParams, setRoutingParams] = useState<{
|
||||
routedTeamMemberIds?: number[];
|
||||
_shouldServeCache?: boolean;
|
||||
skipContactOwner?: boolean;
|
||||
isBookingDryRun?: boolean;
|
||||
}>({});
|
||||
|
||||
useEffect(() => {
|
||||
const searchParams = props.routingFormSearchParams
|
||||
? new URLSearchParams(props.routingFormSearchParams)
|
||||
: new URLSearchParams(window.location.search);
|
||||
|
||||
const routedTeamMemberIds = getRoutedTeamMemberIdsFromSearchParams(searchParams);
|
||||
const skipContactOwner = searchParams.get("cal.skipContactOwner") === "true";
|
||||
|
||||
const _cacheParam = searchParams?.get("cal.cache");
|
||||
const _shouldServeCache = _cacheParam ? _cacheParam === "true" : undefined;
|
||||
const isBookingDryRun =
|
||||
searchParams?.get("cal.isBookingDryRun")?.toLowerCase() === "true" ||
|
||||
searchParams?.get("cal.sandbox")?.toLowerCase() === "true";
|
||||
setRoutingParams({
|
||||
...(skipContactOwner ? { skipContactOwner } : {}),
|
||||
...(routedTeamMemberIds ? { routedTeamMemberIds } : {}),
|
||||
...(_shouldServeCache ? { _shouldServeCache } : {}),
|
||||
...(isBookingDryRun ? { isBookingDryRun } : {}),
|
||||
});
|
||||
}, [props.routingFormSearchParams]);
|
||||
const bookingData = useBookerStoreContext((state) => state.bookingData);
|
||||
const setBookingData = useBookerStoreContext((state) => state.setBookingData);
|
||||
|
||||
useGetBookingForReschedule({
|
||||
uid: props.rescheduleUid ?? props.bookingUid ?? "",
|
||||
onSuccess: (data) => {
|
||||
setBookingData(data);
|
||||
},
|
||||
});
|
||||
|
||||
useInitializeBookerStoreContext({
|
||||
...props,
|
||||
teamMemberEmail,
|
||||
crmAppSlug,
|
||||
crmOwnerRecordType,
|
||||
crmRecordId: props.crmRecordId,
|
||||
eventId: event?.data?.id,
|
||||
rescheduleUid: props.rescheduleUid ?? null,
|
||||
bookingUid: props.bookingUid ?? null,
|
||||
layout: "week_view",
|
||||
org: props.entity?.orgSlug,
|
||||
username,
|
||||
bookingData,
|
||||
isPlatform: true,
|
||||
allowUpdatingUrlParams,
|
||||
allowUpdatingUrlParams: false,
|
||||
});
|
||||
|
||||
const schedule = useAvailableSlots({
|
||||
@@ -160,11 +115,10 @@ const CalendarViewPlatformWrapperComponent = (
|
||||
endTime,
|
||||
timeZone: timezone,
|
||||
duration: selectedDuration ?? undefined,
|
||||
rescheduleUid: props.rescheduleUid,
|
||||
teamMemberEmail: props.teamMemberEmail ?? undefined,
|
||||
...(props.isTeamEvent
|
||||
teamMemberEmail: undefined,
|
||||
...(isTeamEvent
|
||||
? {
|
||||
isTeamEvent: props.isTeamEvent,
|
||||
isTeamEvent: isTeamEvent,
|
||||
teamId: teamId,
|
||||
}
|
||||
: {}),
|
||||
@@ -172,11 +126,10 @@ const CalendarViewPlatformWrapperComponent = (
|
||||
Boolean(teamId || username) &&
|
||||
Boolean(month) &&
|
||||
Boolean(timezone) &&
|
||||
(props.isTeamEvent ? !isTeamPending : !isPending) &&
|
||||
(isTeamEvent ? !isTeamPending : !isPending) &&
|
||||
Boolean(event?.data?.id),
|
||||
orgSlug: props.entity?.orgSlug ?? undefined,
|
||||
eventTypeSlug: isDynamic ? "dynamic" : eventSlug || "",
|
||||
...routingParams,
|
||||
orgSlug: undefined,
|
||||
eventTypeSlug: isDynamic ? "dynamic" : props.eventSlug || "",
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -185,7 +138,7 @@ const CalendarViewPlatformWrapperComponent = (
|
||||
<Header
|
||||
isCalendarView={true}
|
||||
isMyLink={true}
|
||||
eventSlug={eventSlug}
|
||||
eventSlug={props.eventSlug}
|
||||
enabledLayouts={bookerLayout.bookerLayouts.enabledLayouts}
|
||||
extraDays={7}
|
||||
isMobile={false}
|
||||
@@ -204,7 +157,9 @@ const CalendarViewPlatformWrapperComponent = (
|
||||
};
|
||||
|
||||
export const CalendarViewPlatformWrapper = (
|
||||
props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam
|
||||
props:
|
||||
| (BookerPlatformWrapperAtomPropsForIndividual & { teamId?: number })
|
||||
| Omit<BookerPlatformWrapperAtomPropsForTeam, "isTeamEvent">
|
||||
) => {
|
||||
return (
|
||||
<BookerStoreProvider>
|
||||
|
||||
Reference in New Issue
Block a user