Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getBulkTeamEventTypes,
getBulkUserEventTypes,
getEventTypeById,
getEventTypeByIdWithTeamMembers,
getPublicEvent,
type PublicEventType,
TUpdateEventTypeInputSchema,
Expand Down Expand Up @@ -99,7 +100,7 @@ export class EventTypesAtomService {
? await this.membershipsRepository.isUserOrganizationAdmin(user.id, organizationId)
: false;

const eventType = await getEventTypeById({
const eventType = await getEventTypeByIdWithTeamMembers({
currentOrganizationId: this.usersService.getUserMainOrgId(user),
eventTypeId,
userId: user.id,
Expand All @@ -121,11 +122,9 @@ export class EventTypesAtomService {
}
}

// note (Lauris): don't show platform owner as one of the people that can be assigned to managed team event type
const onlyManagedTeamMembers = eventType.teamMembers.filter((user) => user.isPlatformManaged);
eventType.teamMembers = onlyManagedTeamMembers;
const onlyManagedTeamMembers = eventType.teamMembers.filter((member) => member.isPlatformManaged);

return eventType;
return { ...eventType, teamMembers: onlyManagedTeamMembers };
}

async getUserEventTypes(userId: number) {
Expand Down
109 changes: 89 additions & 20 deletions apps/web/modules/event-types/components/AddMembersWithSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import type {
FormValues,
Host,
SettingsToggleClassNames,
TeamMember,
} from "@calcom/features/eventtypes/lib/types";
import { useSearchTeamMembers } from "@calcom/features/eventtypes/lib/useSearchTeamMembers";
import { Segment } from "./Segment";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { AttributesQueryValue } from "@calcom/lib/raqb/types";
import { AssignedSearchInput } from "@calcom/features/eventtypes/components/AssignedSearchInput";
import { Label, SettingsToggle } from "@calcom/ui/components/form";
import { type ComponentProps, type Dispatch, type SetStateAction, useMemo } from "react";
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
import { type ComponentProps, type Dispatch, type SetStateAction, useMemo, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import type { Options } from "react-select";
import { AddMembersWithSwitchWebWrapper } from "./AddMembersWithSwitchWebWrapper";
Expand Down Expand Up @@ -42,7 +44,7 @@ export const mapUserToValue = (
defaultScheduleId,
});

const sortByLabel = (a: ReturnType<typeof mapUserToValue>, b: ReturnType<typeof mapUserToValue>) => {
const sortByLabel = (a: { label: string }, b: { label: string }) => {
if (a.label < b.label) {
return -1;
}
Expand All @@ -63,6 +65,12 @@ const CheckedHostField = ({
isRRWeightsEnabled,
groupId,
customClassNames,
onSearchChange,
onMenuScrollToBottom,
isLoadingMore,
hasNextPageSelected,
isFetchingNextPageSelected,
fetchNextPageSelected,
...rest
}: {
labelText?: string;
Expand All @@ -74,7 +82,14 @@ const CheckedHostField = ({
helperText?: React.ReactNode | string;
isRRWeightsEnabled?: boolean;
groupId: string | null;
onSearchChange?: (search: string) => void;
onMenuScrollToBottom?: () => void;
isLoadingMore?: boolean;
hasNextPageSelected?: boolean;
isFetchingNextPageSelected?: boolean;
fetchNextPageSelected?: () => void;
} & Omit<Partial<ComponentProps<typeof CheckedTeamSelect>>, "onChange" | "value">) => {
const { t } = useLocale();
return (
<div className="flex flex-col rounded-md">
<div>
Expand All @@ -98,16 +113,30 @@ const CheckedHostField = ({
.filter(({ isFixed: _isFixed }) => isFixed === _isFixed)
.reduce((acc, host) => {
const option = options.find((member) => member.value === host.userId.toString());
if (!option) return acc;

acc.push({
...option,
priority: host.priority ?? 2,
isFixed,
weight: host.weight ?? 100,
groupId: host.groupId,
});
// Use option data if available, otherwise create a fallback from host metadata
// (host data from usePaginatedAssignmentHosts includes name/email/avatarUrl at runtime)
const hostAny = host as Host & { name?: string | null; email?: string; avatarUrl?: string | null };
const displayOption: CheckedSelectOption = option
? {
...option,
priority: host.priority ?? 2,
isFixed,
weight: host.weight ?? 100,
groupId: host.groupId,
}
: {
value: host.userId.toString(),
label: hostAny.name || hostAny.email || t("team_member"),
avatar: hostAny.avatarUrl || "",
priority: host.priority ?? 2,
isFixed,
weight: host.weight ?? 100,
groupId: host.groupId,
defaultScheduleId: host.scheduleId,
};

acc.push(displayOption);
return acc;
}, [] as CheckedSelectOption[])}
controlShouldRenderValue={false}
Expand All @@ -116,6 +145,13 @@ const CheckedHostField = ({
isRRWeightsEnabled={isRRWeightsEnabled}
customClassNames={customClassNames}
groupId={groupId}
hosts={value}
onSearchChange={onSearchChange}
onMenuScrollToBottom={onMenuScrollToBottom}
isLoadingMore={isLoadingMore}
hasNextPageSelected={hasNextPageSelected}
isFetchingNextPageSelected={isFetchingNextPageSelected}
fetchNextPageSelected={fetchNextPageSelected}
{...rest}
/>
</div>
Expand All @@ -130,15 +166,13 @@ function MembersSegmentWithToggle({
rrSegmentQueryValue,
setRrSegmentQueryValue,
className,
filterMemberIds,
}: {
teamId: number;
assignRRMembersUsingSegment: boolean;
setAssignRRMembersUsingSegment: (value: boolean) => void;
rrSegmentQueryValue: AttributesQueryValue | null;
setRrSegmentQueryValue: (value: AttributesQueryValue) => void;
className?: string;
filterMemberIds?: number[];
}) {
const { t } = useLocale();
const onQueryValueChange = ({ queryValue }: { queryValue: AttributesQueryValue }) => {
Expand All @@ -164,7 +198,6 @@ function MembersSegmentWithToggle({
queryValue={rrSegmentQueryValue}
onQueryValueChange={onQueryValueChange}
className={className}
filterMemberIds={filterMemberIds}
/>
)}
</SettingsToggle>
Expand All @@ -179,7 +212,6 @@ export type AddMembersWithSwitchCustomClassNames = {
};

export type AddMembersWithSwitchProps = {
teamMembers: TeamMember[];
value: Host[];
onChange: (hosts: Host[]) => void;
assignAllTeamMembers: boolean;
Expand All @@ -194,6 +226,12 @@ export type AddMembersWithSwitchProps = {
groupId: string | null;
"data-testid"?: string;
customClassNames?: AddMembersWithSwitchCustomClassNames;
hasNextPageSelected?: boolean;
isFetchingNextPageSelected?: boolean;
fetchNextPageSelected?: () => void;
assignedSearchValue?: string;
onAssignedSearchChange?: (value: string) => void;
isSearchingAssigned?: boolean;
};

enum AssignmentState {
Expand Down Expand Up @@ -246,7 +284,6 @@ function useSegmentState() {
}

export function AddMembersWithSwitch({
teamMembers,
value,
onChange,
assignAllTeamMembers,
Expand All @@ -260,10 +297,27 @@ export function AddMembersWithSwitch({
isSegmentApplicable,
groupId,
customClassNames,
hasNextPageSelected,
isFetchingNextPageSelected,
fetchNextPageSelected,
...rest
}: AddMembersWithSwitchProps) {
const { t } = useLocale();
const { setValue } = useFormContext<FormValues>();

const [search, setSearch] = useState("");
const debouncedSearch = useDebounce(search, 300);

const {
options: searchOptions,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of mapping through all team members we just map through the displayed options

fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useSearchTeamMembers({
teamId,
search: debouncedSearch,
enabled: true,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: useSearchTeamMembers is always enabled, so it will fetch paginated team members even when the assignment state hides the member selector (e.g., assign-all or segment modes). This adds unnecessary network requests and processing. Consider enabling the query only when the selector is rendered.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/modules/event-types/components/AddMembersWithSwitch.tsx, line 319:

<comment>`useSearchTeamMembers` is always enabled, so it will fetch paginated team members even when the assignment state hides the member selector (e.g., assign-all or segment modes). This adds unnecessary network requests and processing. Consider enabling the query only when the selector is rendered.</comment>

<file context>
@@ -260,10 +297,27 @@ export function AddMembersWithSwitch({
+  } = useSearchTeamMembers({
+    teamId,
+    search: debouncedSearch,
+    enabled: true,
+  });
   const {
</file context>
Fix with Cubic

});
const {
assignRRMembersUsingSegment,
setAssignRRMembersUsingSegment,
Expand Down Expand Up @@ -306,7 +360,6 @@ export function AddMembersWithSwitch({
setAssignRRMembersUsingSegment={setAssignRRMembersUsingSegment}
rrSegmentQueryValue={rrSegmentQueryValue}
setRrSegmentQueryValue={setRrSegmentQueryValue}
filterMemberIds={value.filter((host) => !host.isFixed).map((host) => host.userId)}
/>
</div>
)}
Expand All @@ -328,23 +381,39 @@ export function AddMembersWithSwitch({
/>
)}
</div>
{rest.onAssignedSearchChange && (
<AssignedSearchInput
value={rest.assignedSearchValue ?? ""}
onChange={rest.onAssignedSearchChange}
isSearching={rest.isSearchingAssigned}
className="mb-2"
/>
)}
<div className="mb-2">
<CheckedHostField
data-testid={rest["data-testid"]}
value={value}
onChange={onChange}
isFixed={isFixed}
className="mb-2"
options={teamMembers
.map((member) => ({
...member,
options={searchOptions
.map((opt) => ({
...opt,
groupId: groupId,
}))
.sort(sortByLabel)}
placeholder={placeholder ?? t("add_attendees")}
isRRWeightsEnabled={isRRWeightsEnabled}
groupId={groupId}
customClassNames={customClassNames?.teamMemberSelect}
onSearchChange={setSearch}
onMenuScrollToBottom={() => {
if (hasNextPage && !isFetchingNextPage) fetchNextPage();
}}
isLoadingMore={isFetchingNextPage}
hasNextPageSelected={hasNextPageSelected}
isFetchingNextPageSelected={isFetchingNextPageSelected}
fetchNextPageSelected={fetchNextPageSelected}
/>
</div>
</>
Expand Down
Loading