Add "Choose common schedule toggle" to team events (#5343)

* Add Choose common schedule togglee

* Fix types

* Add translations

* Improve variable name

* Move setting to config so that all such lightweight boolean settings can exist here

* Update apps/web/public/static/locales/en/common.json

Co-authored-by: Alex van Andel <me@alexvanandel.com>

Co-authored-by: Alex van Andel <me@alexvanandel.com>
pull/5261/head^2
Hariom Balhara 2022-11-03 19:54:07 +05:30 committed by GitHub
parent 328f00a9b5
commit d751cca0f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 144 additions and 93 deletions

View File

@ -9,7 +9,7 @@ import { weekdayNames } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Icon } from "@calcom/ui";
import { Badge } from "@calcom/ui/v2";
import { Badge, SettingsToggle } from "@calcom/ui/v2";
import Button from "@calcom/ui/v2/core/Button";
import Select from "@calcom/ui/v2/core/form/select";
import { SkeletonText } from "@calcom/ui/v2/core/skeleton";
@ -97,19 +97,15 @@ const format = (date: Date, hour12: boolean) =>
new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss"))
);
export const AvailabilityTab = () => {
export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
const { t, i18n } = useLocale();
const { watch } = useFormContext<FormValues>();
const EventTypeSchedule = () => {
const me = useMeQuery();
const timeFormat = me?.data?.timeFormat;
const scheduleId = watch("schedule");
const { isLoading, data: schedule } = trpc.useQuery(["viewer.availability.schedule", { scheduleId }]);
const filterDays = (dayNum: number) =>
schedule?.schedule.availability.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
return (
<>
<div className="space-y-4">
<div>
<div className="min-w-4 mb-2">
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-neutral-700">
@ -130,7 +126,6 @@ export const AvailabilityTab = () => {
)}
/>
</div>
<div className="space-y-4 rounded border p-4 py-6 pt-2 md:p-8">
<ol className="table border-collapse text-sm">
{weekdayNames(i18n.language, 1, "long").map((day, index) => {
@ -153,7 +148,7 @@ export const AvailabilityTab = () => {
<span className="w-16 sm:w-28 sm:text-left">
{format(dayRange.startTime, timeFormat === 12)}
</span>
<span className="ml-4 sm:ml-0">-</span>
<span className="ml-4">-</span>
<div className="ml-6">{format(dayRange.endTime, timeFormat === 12)}</div>
</div>
))}
@ -182,6 +177,34 @@ export const AvailabilityTab = () => {
</Button>
</div>
</div>
</>
</div>
);
};
const scheduleId = watch("schedule");
const { isLoading, data: schedule } = trpc.useQuery(["viewer.availability.schedule", { scheduleId }]);
const filterDays = (dayNum: number) =>
schedule?.schedule.availability.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
if (!isTeamEvent) {
return <EventTypeSchedule />;
}
return (
<Controller
name="metadata.config.useHostSchedulesForTeamEvent"
render={({ field: { value, onChange } }) => (
<SettingsToggle
checked={!value}
onCheckedChange={(checked) => {
onChange(!checked);
}}
title={t("choose_common_schedule_team_event")}
description={t("choose_common_schedule_team_event_description")}>
<EventTypeSchedule />
</SettingsToggle>
)}
/>
);
};

View File

@ -192,7 +192,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
teamMembers={teamMembers}
/>
),
availability: <AvailabilityTab />,
availability: <AvailabilityTab isTeamEvent={!!team} />,
team: (
<EventTeamTab
eventType={eventType}

View File

@ -1339,5 +1339,7 @@
"number_sms_notifications": "Phone number (SMS\u00a0notifications)",
"attendee_email_workflow": "Attendee email",
"attendee_email_info": "The person booking's email",
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again."
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
"choose_common_schedule_team_event": "Choose a common schedule",
"choose_common_schedule_team_event_description": "Enable this if you want to use a common schedule between hosts. When disabled, each host will be booked based on their default schedule."
}

View File

@ -9,7 +9,7 @@ import logger from "@calcom/lib/logger";
import { checkLimit } from "@calcom/lib/server";
import { performance } from "@calcom/lib/server/perfObserver";
import prisma, { availabilityUserSelect } from "@calcom/prisma";
import { stringToDayjs } from "@calcom/prisma/zod-utils";
import { EventTypeMetaDataSchema, stringToDayjs } from "@calcom/prisma/zod-utils";
import { BookingLimit, EventBusyDetails } from "@calcom/types/Calendar";
import { getBusyTimes } from "./getBusyTimes";
@ -27,14 +27,15 @@ const availabilitySchema = z
})
.refine((data) => !!data.username || !!data.userId, "Either username or userId should be filled in.");
const getEventType = (id: number) =>
prisma.eventType.findUnique({
const getEventType = async (id: number) => {
const eventType = await prisma.eventType.findUnique({
where: { id },
select: {
id: true,
seatsPerTimeSlot: true,
bookingLimits: true,
timeZone: true,
metadata: true,
schedule: {
select: {
availability: true,
@ -50,6 +51,14 @@ const getEventType = (id: number) =>
},
},
});
if (!eventType) {
return eventType;
}
return {
...eventType,
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
};
};
type EventType = Awaited<ReturnType<typeof getEventType>>;
@ -200,7 +209,9 @@ export async function getUserAvailability(
});
}
}
const schedule = eventType?.schedule
const schedule =
!eventType?.metadata?.config?.useHostSchedulesForTeamEvent && eventType?.schedule
? { ...eventType?.schedule }
: {
...currentUser.schedules.filter(

View File

@ -32,6 +32,11 @@ export const EventTypeMetaDataSchema = z
giphyThankYouPage: z.string().optional(),
apps: z.object(appDataSchemas).partial().optional(),
additionalNotesRequired: z.boolean().optional(),
config: z
.object({
useHostSchedulesForTeamEvent: z.boolean().optional(),
})
.optional(),
})
.nullable();

View File

@ -11,6 +11,7 @@ import logger from "@calcom/lib/logger";
import { performance } from "@calcom/lib/server/perfObserver";
import getTimeSlots from "@calcom/lib/slots";
import prisma, { availabilityUserSelect } from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { EventBusyDate } from "@calcom/types/Calendar";
import { TimeRange } from "@calcom/types/schedule";
@ -104,7 +105,7 @@ export const slotsRouter = createRouter().query("getSchedule", {
});
async function getEventType(ctx: { prisma: typeof prisma }, input: z.infer<typeof getScheduleSchema>) {
return ctx.prisma.eventType.findUnique({
const eventType = await ctx.prisma.eventType.findUnique({
where: {
id: input.eventTypeId,
},
@ -124,6 +125,7 @@ async function getEventType(ctx: { prisma: typeof prisma }, input: z.infer<typeo
periodEndDate: true,
periodCountCalendarDays: true,
periodDays: true,
metadata: true,
schedule: {
select: {
availability: true,
@ -144,6 +146,14 @@ async function getEventType(ctx: { prisma: typeof prisma }, input: z.infer<typeo
},
},
});
if (!eventType) {
return eventType;
}
return {
...eventType,
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
};
}
async function getDynamicEventType(ctx: { prisma: typeof prisma }, input: z.infer<typeof getScheduleSchema>) {

View File

@ -44,8 +44,8 @@ function SettingsToggle({
</div>
</div>
{children && (
<div className="mt-4 lg:ml-14" ref={animateRef}>
{checked && children}
<div className="lg:ml-14" ref={animateRef}>
{checked && <div className="mt-4">{children}</div>}
</div>
)}
</fieldset>