fix: troubleshooter team events + improve race condition (#25704)
* fix query to bring back teams + fix hover state * fix team issues with troubleshooter
This commit is contained in:
@@ -31,7 +31,7 @@ export function EventScheduleItem() {
|
||||
suffixSlot={
|
||||
schedule && (
|
||||
<Link href={`/availability/${schedule.id}`} className="inline-flex">
|
||||
<Badge color="orange" size="sm" className="hidden hover:cursor-pointer group-hover:inline-flex">
|
||||
<Badge color="orange" size="sm" className="invisible hover:cursor-pointer group-hover:visible">
|
||||
{t("edit")}
|
||||
</Badge>
|
||||
</Link>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo, useEffect, startTransition } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { SelectField } from "@calcom/ui/components/form";
|
||||
@@ -7,67 +8,80 @@ import { getQueryParam } from "../../bookings/Booker/utils/query-param";
|
||||
import { useTroubleshooterStore } from "../store";
|
||||
|
||||
export function EventTypeSelect() {
|
||||
const { data: eventTypes, isPending } = trpc.viewer.eventTypes.list.useQuery();
|
||||
const selectedEventType = useTroubleshooterStore((state) => state.event);
|
||||
const setSelectedEventType = useTroubleshooterStore((state) => state.setEvent);
|
||||
|
||||
const selectedEventQueryParam = getQueryParam("eventType");
|
||||
const { data: eventTypes, isPending } = trpc.viewer.eventTypes.listWithTeam.useQuery();
|
||||
const { event: selectedEventType, setEvent: setSelectedEventType } = useTroubleshooterStore(
|
||||
(state) => ({
|
||||
event: state.event,
|
||||
setEvent: state.setEvent,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!eventTypes) return [];
|
||||
return eventTypes.map((e) => ({
|
||||
label: e.title,
|
||||
value: e.slug,
|
||||
value: e.id.toString(),
|
||||
id: e.id,
|
||||
duration: e.length,
|
||||
}));
|
||||
}, [eventTypes]);
|
||||
|
||||
// Initialize event type from query param or default to first event
|
||||
useEffect(() => {
|
||||
if (!selectedEventType && eventTypes && eventTypes[0] && !selectedEventQueryParam) {
|
||||
const { id, slug, length } = eventTypes[0];
|
||||
setSelectedEventType({
|
||||
id,
|
||||
slug,
|
||||
duration: length,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [eventTypes]);
|
||||
if (!eventTypes || eventTypes.length === 0) return;
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventQueryParam) {
|
||||
// ensure that the update is deferred until the Suspense boundary has finished hydrating
|
||||
const selectedEventIdParam = getQueryParam("eventTypeId");
|
||||
const eventTypeId = selectedEventIdParam ? parseInt(selectedEventIdParam, 10) : null;
|
||||
|
||||
// If we already have a selected event that matches the query param, don't do anything
|
||||
if (selectedEventType?.id === eventTypeId) return;
|
||||
|
||||
// If there's a query param, try to find and set that event
|
||||
if (eventTypeId && !isNaN(eventTypeId)) {
|
||||
startTransition(() => {
|
||||
const foundEventType = eventTypes?.find((et) => et.slug === selectedEventQueryParam);
|
||||
const foundEventType = eventTypes.find((et) => et.id === eventTypeId);
|
||||
if (foundEventType) {
|
||||
const { id, slug, length } = foundEventType;
|
||||
setSelectedEventType({ id, slug, duration: length });
|
||||
} else if (eventTypes && eventTypes[0]) {
|
||||
const { id, slug, length } = eventTypes[0];
|
||||
setSelectedEventType({
|
||||
id,
|
||||
slug,
|
||||
duration: length,
|
||||
id: foundEventType.id,
|
||||
slug: foundEventType.slug,
|
||||
duration: foundEventType.length,
|
||||
teamId: foundEventType.team?.id ?? null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [eventTypes, selectedEventQueryParam, setSelectedEventType]);
|
||||
|
||||
// If no event is selected and no valid query param, default to first event
|
||||
if (!selectedEventType && !eventTypeId) {
|
||||
const firstEvent = eventTypes[0];
|
||||
setSelectedEventType({
|
||||
id: firstEvent.id,
|
||||
slug: firstEvent.slug,
|
||||
duration: firstEvent.length,
|
||||
teamId: firstEvent.team?.id ?? null,
|
||||
});
|
||||
}
|
||||
}, [eventTypes, selectedEventType, setSelectedEventType]);
|
||||
|
||||
return (
|
||||
<SelectField
|
||||
label="Event Type"
|
||||
options={options}
|
||||
isDisabled={isPending || options.length === 0}
|
||||
value={options.find((option) => option.value === selectedEventType?.slug) || options[0]}
|
||||
value={options.find((option) => option.id === selectedEventType?.id) || options[0]}
|
||||
onChange={(option) => {
|
||||
if (!option) return;
|
||||
setSelectedEventType({
|
||||
slug: option.value,
|
||||
id: option.id,
|
||||
duration: option.duration,
|
||||
});
|
||||
const foundEventType = eventTypes?.find((et) => et.id === option.id);
|
||||
if (foundEventType) {
|
||||
setSelectedEventType({
|
||||
id: foundEventType.id,
|
||||
slug: foundEventType.slug,
|
||||
duration: foundEventType.length,
|
||||
teamId: foundEventType.team?.id ?? null,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ export const LargeCalendar = ({ extraDays }: { extraDays: number }) => {
|
||||
.add(extraDays - 1, "day")
|
||||
.utc()
|
||||
.format(),
|
||||
eventTypeId: event?.id,
|
||||
withSource: true,
|
||||
},
|
||||
{
|
||||
@@ -35,13 +36,16 @@ export const LargeCalendar = ({ extraDays }: { extraDays: number }) => {
|
||||
}
|
||||
);
|
||||
|
||||
const isTeamEvent = !!event?.teamId;
|
||||
const { data: schedule } = useSchedule({
|
||||
username: session?.user.username || "",
|
||||
eventSlug: event?.slug,
|
||||
// For team events, don't pass eventSlug to avoid slug lookup issues - use eventId instead
|
||||
eventSlug: isTeamEvent ? null : event?.slug,
|
||||
eventId: event?.id,
|
||||
timezone,
|
||||
month: startDate.format("YYYY-MM"),
|
||||
orgSlug: session?.user.org?.slug,
|
||||
isTeamEvent,
|
||||
});
|
||||
|
||||
const endDate = dayjs(startDate)
|
||||
|
||||
@@ -17,6 +17,7 @@ type EventType = {
|
||||
id: number;
|
||||
slug: string;
|
||||
duration: number;
|
||||
teamId?: number | null;
|
||||
};
|
||||
|
||||
export type TroubleshooterStore = {
|
||||
@@ -76,7 +77,7 @@ export const useTroubleshooterStore = create<TroubleshooterStore>((set, get) =>
|
||||
event: null,
|
||||
setEvent: (event: EventType) => {
|
||||
set({ event });
|
||||
updateQueryParam("eventType", event.slug ?? "");
|
||||
updateQueryParam("eventTypeId", event.id.toString());
|
||||
},
|
||||
month: getQueryParam("month") || getQueryParam("date") || dayjs().format("YYYY-MM"),
|
||||
setMonth: (month: string | null) => {
|
||||
|
||||
@@ -11,19 +11,26 @@ type ListWithTeamOptions = {
|
||||
|
||||
export const listWithTeamHandler = async ({ ctx }: ListWithTeamOptions) => {
|
||||
const userId = ctx.user.id;
|
||||
const query = Prisma.sql`SELECT "public"."EventType"."id", "public"."EventType"."teamId", "public"."EventType"."title", "public"."EventType"."slug", "j1"."name" as "teamName"
|
||||
const query = Prisma.sql`SELECT "public"."EventType"."id", "public"."EventType"."teamId", "public"."EventType"."title", "public"."EventType"."slug", "public"."EventType"."length", "j1"."name" as "teamName"
|
||||
FROM "public"."EventType"
|
||||
LEFT JOIN "public"."Team" AS "j1" ON ("j1"."id") = ("public"."EventType"."teamId")
|
||||
WHERE "public"."EventType"."userId" = ${userId}
|
||||
UNION
|
||||
SELECT "public"."EventType"."id", "public"."EventType"."teamId", "public"."EventType"."title", "public"."EventType"."slug", "j1"."name" as "teamName"
|
||||
SELECT "public"."EventType"."id", "public"."EventType"."teamId", "public"."EventType"."title", "public"."EventType"."slug", "public"."EventType"."length", "j1"."name" as "teamName"
|
||||
FROM "public"."EventType"
|
||||
INNER JOIN "public"."Team" AS "j1" ON ("j1"."id") = ("public"."EventType"."teamId")
|
||||
INNER JOIN "public"."Membership" AS "t2" ON "t2"."teamId" = "j1"."id"
|
||||
WHERE "t2"."userId" = ${userId} AND "t2"."accepted" = true`;
|
||||
|
||||
const result = await db.$queryRaw<
|
||||
{ id: number; teamId: number | null; title: string; slug: string; teamName: string | null }[]
|
||||
{
|
||||
id: number;
|
||||
teamId: number | null;
|
||||
title: string;
|
||||
slug: string;
|
||||
length: number;
|
||||
teamName: string | null;
|
||||
}[]
|
||||
>(query);
|
||||
|
||||
return result.map((row) => ({
|
||||
@@ -31,5 +38,6 @@ export const listWithTeamHandler = async ({ ctx }: ListWithTeamOptions) => {
|
||||
team: row.teamId ? { id: row.teamId, name: row.teamName || "" } : null,
|
||||
title: row.title,
|
||||
slug: row.slug,
|
||||
length: row.length,
|
||||
}));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user