Skip to content

Commit 38a7fcf

Browse files
fix: filter segment members to only show those with host entries (calcom#26816)
## What does this PR do? Fixes a bug where the UI shows team members who match segment criteria even when they don't have host entries in the database. When `assignAllTeamMembers` is `true` and segment filtering is enabled, members would appear in the Assignment tab but wouldn't actually be assigned during booking because they lack host entries. **The Problem:** - The filtering logic in `EditWeightsForAllTeamMembers` short-circuited when `assignAllTeamMembers` was true, showing all segment-matching members regardless of whether they had host entries - The `handleSave` function only updates hosts that already exist in the `value` array, filtering out members without host entries - This created a misleading UI where members appeared but wouldn't be assigned **The Fix:** - Removed the `assignAllTeamMembers` prop from `EditWeightsForAllTeamMembers` component entirely since the filtering should always check for existing host entries - Updated the filter in `EditWeightsForAllTeamMembers.tsx` to always check for existing host entries in the `value` array - Added `filterMemberIds` prop to `Segment` component to filter matching team members to only show those who are hosts of the event-type - `AddMembersWithSwitch` now passes the list of non-fixed host user IDs to the Segment component via `filterMemberIds` ## Mandatory Tasks (DO NOT REMOVE) - [x] I have self-reviewed the code (A decent size PR without self-review might be rejected). - [x] I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). N/A - no documentation changes needed. - [ ] I confirm automated tests are in place that prove my fix is effective or that my feature works. ## How should this be tested? 1. Create a team event type with round-robin scheduling 2. Enable "Assign all team members" toggle 3. Enable segment filtering with criteria that matches some team members 4. Verify that only members who have host entries (are actually assigned to the event type) appear in the "Edit team member weights" sheet 5. Members matching segment criteria but without host entries should NOT be displayed ## Checklist - [x] My code follows the style guidelines of this project - [x] I have checked if my changes generate no new warnings --- ### Human Review Checklist - [ ] Verify the filtering logic correctly shows only members with host entries in both `EditWeightsForAllTeamMembers` and `Segment` components - [ ] Confirm this aligns with the actual booking behavior (members without host entries won't be assigned) - [ ] Check that `filterMemberIds` prop is correctly passed through `AddMembersWithSwitch` → `MembersSegmentWithToggle` → `Segment` → `MatchingTeamMembers` - [ ] Verify the filter `value.filter((host) => !host.isFixed).map((host) => host.userId)` correctly extracts non-fixed host IDs --- Link to Devin run: https://app.devin.ai/sessions/3333b41226d0452e98ef7ac358511f5b Requested by: @hariombalhara
1 parent e620264 commit 38a7fcf

4 files changed

Lines changed: 77 additions & 84 deletions

File tree

apps/web/modules/event-types/components/AddMembersWithSwitch.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
1-
import { useMemo, type ComponentProps, type Dispatch, type SetStateAction } from "react";
2-
import { useFormContext } from "react-hook-form";
3-
import { Controller } from "react-hook-form";
4-
import type { Options } from "react-select";
5-
61
import { AddMembersWithSwitchPlatformWrapper } from "@calcom/atoms/add-members-switch/AddMembersWithSwitchPlatformWrapper";
7-
import { AddMembersWithSwitchWebWrapper } from "./AddMembersWithSwitchWebWrapper";
82
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
9-
import { Segment } from "@calcom/features/Segment";
103
import type {
114
FormValues,
125
Host,
136
SettingsToggleClassNames,
147
TeamMember,
158
} from "@calcom/features/eventtypes/lib/types";
9+
import { Segment } from "@calcom/features/Segment";
1610
import { useLocale } from "@calcom/lib/hooks/useLocale";
1711
import type { AttributesQueryValue } from "@calcom/lib/raqb/types";
18-
import { Label } from "@calcom/ui/components/form";
19-
import { SettingsToggle } from "@calcom/ui/components/form";
12+
import { Label, SettingsToggle } from "@calcom/ui/components/form";
13+
import { type ComponentProps, type Dispatch, type SetStateAction, useMemo } from "react";
14+
import { Controller, useFormContext } from "react-hook-form";
15+
import type { Options } from "react-select";
16+
import { AddMembersWithSwitchWebWrapper } from "./AddMembersWithSwitchWebWrapper";
2017

2118
import AssignAllTeamMembers from "./AssignAllTeamMembers";
22-
import CheckedTeamSelect from "./CheckedTeamSelect";
2319
import type { CheckedSelectOption, CheckedTeamSelectCustomClassNames } from "./CheckedTeamSelect";
20+
import CheckedTeamSelect from "./CheckedTeamSelect";
2421

2522
interface IUserToValue {
2623
id: number | null;
@@ -130,13 +127,15 @@ function MembersSegmentWithToggle({
130127
rrSegmentQueryValue,
131128
setRrSegmentQueryValue,
132129
className,
130+
filterMemberIds,
133131
}: {
134132
teamId: number;
135133
assignRRMembersUsingSegment: boolean;
136134
setAssignRRMembersUsingSegment: (value: boolean) => void;
137135
rrSegmentQueryValue: AttributesQueryValue | null;
138136
setRrSegmentQueryValue: (value: AttributesQueryValue) => void;
139137
className?: string;
138+
filterMemberIds?: number[];
140139
}) {
141140
const { t } = useLocale();
142141
const onQueryValueChange = ({ queryValue }: { queryValue: AttributesQueryValue }) => {
@@ -162,6 +161,7 @@ function MembersSegmentWithToggle({
162161
queryValue={rrSegmentQueryValue}
163162
onQueryValueChange={onQueryValueChange}
164163
className={className}
164+
filterMemberIds={filterMemberIds}
165165
/>
166166
)}
167167
</SettingsToggle>
@@ -193,7 +193,7 @@ export type AddMembersWithSwitchProps = {
193193
customClassNames?: AddMembersWithSwitchCustomClassNames;
194194
};
195195

196-
const enum AssignmentState {
196+
enum AssignmentState {
197197
TOGGLES_OFF_AND_ALL_TEAM_MEMBERS_NOT_APPLICABLE = "TOGGLES_OFF_AND_ALL_TEAM_MEMBERS_NOT_APPLICABLE",
198198
TOGGLES_OFF_AND_ALL_TEAM_MEMBERS_APPLICABLE = "TOGGLES_OFF_AND_ALL_TEAM_MEMBERS_APPLICABLE",
199199
ALL_TEAM_MEMBERS_ENABLED_AND_SEGMENT_APPLICABLE = "ALL_TEAM_MEMBERS_ENABLED_AND_SEGMENT_APPLICABLE",
@@ -303,6 +303,7 @@ export function AddMembersWithSwitch({
303303
setAssignRRMembersUsingSegment={setAssignRRMembersUsingSegment}
304304
rrSegmentQueryValue={rrSegmentQueryValue}
305305
setRrSegmentQueryValue={setRrSegmentQueryValue}
306+
filterMemberIds={value.filter((host) => !host.isFixed).map((host) => host.userId)}
306307
/>
307308
</div>
308309
)}

apps/web/modules/event-types/components/EditWeightsForAllTeamMembers.tsx

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
"use client";
22

3-
import Link from "next/link";
4-
import { useState, useEffect, useRef, useMemo } from "react";
5-
3+
import { useTeamMembersWithSegmentPlatform } from "@calcom/atoms/event-types/hooks/useTeamMembersWithSegmentPlatform";
64
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
75
import type { Host, TeamMember } from "@calcom/features/eventtypes/lib/types";
86
import ServerTrans from "@calcom/lib/components/ServerTrans";
97
import { downloadAsCsv } from "@calcom/lib/csvUtils";
108
import { useLocale } from "@calcom/lib/hooks/useLocale";
119
import type { AttributesQueryValue } from "@calcom/lib/raqb/types";
1210
import { Avatar } from "@calcom/ui/components/avatar";
13-
import { buttonClasses } from "@calcom/ui/components/button";
14-
import { Button } from "@calcom/ui/components/button";
11+
import { Button, buttonClasses } from "@calcom/ui/components/button";
1512
import { TextField } from "@calcom/ui/components/form";
1613
import { Icon } from "@calcom/ui/components/icon";
1714
import {
@@ -24,11 +21,9 @@ import {
2421
SheetTitle,
2522
} from "@calcom/ui/components/sheet";
2623
import { showToast } from "@calcom/ui/components/toast";
27-
28-
import {
29-
useTeamMembersWithSegmentPlatform,
30-
} from "@calcom/atoms/event-types/hooks/useTeamMembersWithSegmentPlatform";
3124
import { useTeamMembersWithSegment } from "@calcom/web/modules/event-types/hooks/useTeamMembersWithSegment";
25+
import Link from "next/link";
26+
import { useEffect, useMemo, useRef, useState } from "react";
3227

3328
type TeamMemberItemProps = {
3429
member: Omit<TeamMember, "defaultScheduleId"> & { weight?: number };
@@ -102,7 +97,6 @@ interface Props {
10297
teamMembers: TeamMember[];
10398
value: Host[];
10499
onChange: (hosts: Host[]) => void;
105-
assignAllTeamMembers: boolean;
106100
assignRRMembersUsingSegment: boolean;
107101
teamId?: number;
108102
queryValue?: AttributesQueryValue | null;
@@ -112,7 +106,6 @@ export const EditWeightsForAllTeamMembers = ({
112106
teamMembers: initialTeamMembers,
113107
value,
114108
onChange,
115-
assignAllTeamMembers,
116109
assignRRMembersUsingSegment,
117110
teamId,
118111
queryValue,
@@ -240,13 +233,9 @@ export const EditWeightsForAllTeamMembers = ({
240233
member.email.toLowerCase().includes(searchQuery.toLowerCase())
241234
)
242235
.filter((member) => {
243-
// When assignAllTeamMembers is false, only include members that exist in value array
244-
return (
245-
assignAllTeamMembers ||
246-
value.some((host) => !host.isFixed && host.userId === parseInt(member.value, 10))
247-
);
236+
return value.some((host) => !host.isFixed && host.userId === parseInt(member.value, 10));
248237
});
249-
}, [teamMembers, localWeights, searchQuery, assignAllTeamMembers, value]);
238+
}, [teamMembers, localWeights, searchQuery, value]);
250239

251240
return (
252241
<>

apps/web/modules/event-types/components/tabs/assignment/EventTeamAssignmentTab.tsx

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,38 @@
1-
import type { TFunction } from "i18next";
2-
import Link from "next/link";
3-
import { useCallback, useEffect, useRef, useState } from "react";
4-
import type { ComponentProps, Dispatch, SetStateAction } from "react";
5-
import { Controller, useFormContext, useWatch } from "react-hook-form";
6-
import type { Options } from "react-select";
7-
import { v4 as uuidv4 } from "uuid";
8-
9-
import type { AddMembersWithSwitchCustomClassNames } from "@calcom/web/modules/event-types/components/AddMembersWithSwitch";
10-
import AddMembersWithSwitch, {
11-
mapUserToValue,
12-
} from "@calcom/web/modules/event-types/components/AddMembersWithSwitch";
13-
import AssignAllTeamMembers from "@calcom/web/modules/event-types/components/AssignAllTeamMembers";
14-
import type { ChildrenEventTypeSelectCustomClassNames } from "@calcom/web/modules/event-types/components/ChildrenEventTypeSelect";
15-
import ChildrenEventTypeSelect from "@calcom/web/modules/event-types/components/ChildrenEventTypeSelect";
16-
import { EditWeightsForAllTeamMembers } from "@calcom/web/modules/event-types/components/EditWeightsForAllTeamMembers";
17-
import { sortHosts } from "@calcom/lib/bookings/hostGroupUtils";
18-
import { LearnMoreLink } from "@calcom/web/modules/event-types/components/LearnMoreLink";
19-
import WeightDescription from "@calcom/web/modules/event-types/components/WeightDescription";
201
import type {
21-
FormValues,
22-
TeamMember,
232
EventTypeSetupProps,
3+
FormValues,
244
Host,
255
SelectClassNames,
266
SettingsToggleClassNames,
7+
TeamMember,
278
} from "@calcom/features/eventtypes/lib/types";
9+
import { sortHosts } from "@calcom/lib/bookings/hostGroupUtils";
2810
import ServerTrans from "@calcom/lib/components/ServerTrans";
2911
import { useLocale } from "@calcom/lib/hooks/useLocale";
3012
import { RRTimestampBasis, SchedulingType } from "@calcom/prisma/enums";
3113
import classNames from "@calcom/ui/classNames";
3214
import { Button } from "@calcom/ui/components/button";
33-
import { Label } from "@calcom/ui/components/form";
34-
import { Select } from "@calcom/ui/components/form";
35-
import { SettingsToggle } from "@calcom/ui/components/form";
15+
import { Label, Select, SettingsToggle } from "@calcom/ui/components/form";
3616
import { Icon } from "@calcom/ui/components/icon";
3717
import { RadioAreaGroup as RadioArea } from "@calcom/ui/components/radio";
3818
import { Tooltip } from "@calcom/ui/components/tooltip";
19+
import type { AddMembersWithSwitchCustomClassNames } from "@calcom/web/modules/event-types/components/AddMembersWithSwitch";
20+
import AddMembersWithSwitch, {
21+
mapUserToValue,
22+
} from "@calcom/web/modules/event-types/components/AddMembersWithSwitch";
23+
import AssignAllTeamMembers from "@calcom/web/modules/event-types/components/AssignAllTeamMembers";
24+
import type { ChildrenEventTypeSelectCustomClassNames } from "@calcom/web/modules/event-types/components/ChildrenEventTypeSelect";
25+
import ChildrenEventTypeSelect from "@calcom/web/modules/event-types/components/ChildrenEventTypeSelect";
26+
import { EditWeightsForAllTeamMembers } from "@calcom/web/modules/event-types/components/EditWeightsForAllTeamMembers";
27+
import { LearnMoreLink } from "@calcom/web/modules/event-types/components/LearnMoreLink";
28+
import WeightDescription from "@calcom/web/modules/event-types/components/WeightDescription";
29+
import type { TFunction } from "i18next";
30+
import Link from "next/link";
31+
import type { ComponentProps, Dispatch, SetStateAction } from "react";
32+
import { useCallback, useEffect, useRef, useState } from "react";
33+
import { Controller, useFormContext, useWatch } from "react-hook-form";
34+
import type { Options } from "react-select";
35+
import { v4 as uuidv4 } from "uuid";
3936

4037
export type EventTeamAssignmentTabCustomClassNames = {
4138
assignmentType?: {
@@ -506,7 +503,6 @@ const RoundRobinHosts = ({
506503
teamMembers={teamMembers}
507504
value={value}
508505
onChange={handleWeightsChange}
509-
assignAllTeamMembers={assignAllTeamMembers}
510506
assignRRMembersUsingSegment={assignRRMembersUsingSegment}
511507
teamId={teamId}
512508
queryValue={rrSegmentQueryValue}
@@ -666,10 +662,10 @@ const Hosts = ({
666662

667663
return existingHost
668664
? {
669-
...newValue,
670-
scheduleId: existingHost.scheduleId,
671-
groupId: existingHost.groupId,
672-
}
665+
...newValue,
666+
scheduleId: existingHost.scheduleId,
667+
groupId: existingHost.groupId,
668+
}
673669
: newValue;
674670
});
675671
};
@@ -724,7 +720,7 @@ const Hosts = ({
724720
),
725721
MANAGED: <></>,
726722
};
727-
return !!schedulingType ? schedulingTypeRender[schedulingType] : <></>;
723+
return schedulingType ? schedulingTypeRender[schedulingType] : <></>;
728724
}}
729725
/>
730726
);
@@ -745,17 +741,17 @@ export const EventTeamAssignmentTab = ({
745741
label: string;
746742
// description: string;
747743
}[] = [
748-
{
749-
value: "COLLECTIVE",
750-
label: t("collective"),
751-
// description: t("collective_description"),
752-
},
753-
{
754-
value: "ROUND_ROBIN",
755-
label: t("round_robin"),
756-
// description: t("round_robin_description"),
757-
},
758-
];
744+
{
745+
value: "COLLECTIVE",
746+
label: t("collective"),
747+
// description: t("collective_description"),
748+
},
749+
{
750+
value: "ROUND_ROBIN",
751+
label: t("round_robin"),
752+
// description: t("round_robin_description"),
753+
},
754+
];
759755
const pendingMembers = (member: (typeof teamMembers)[number]) =>
760756
!!eventType.team?.parentId || !!member.username;
761757
const teamMembersOptions = teamMembers
@@ -887,13 +883,11 @@ export const EventTeamAssignmentTab = ({
887883
</RadioArea.Item>
888884
{(eventType.team?.rrTimestampBasis &&
889885
eventType.team?.rrTimestampBasis !== RRTimestampBasis.CREATED_AT) ||
890-
hostGroups?.length > 1 ? (
886+
hostGroups?.length > 1 ? (
891887
<Tooltip
892888
content={
893-
!!(
894-
eventType.team?.rrTimestampBasis &&
889+
eventType.team?.rrTimestampBasis &&
895890
eventType.team?.rrTimestampBasis !== RRTimestampBasis.CREATED_AT
896-
)
897891
? t("rr_load_balancing_disabled")
898892
: t("rr_load_balancing_disabled_with_groups")
899893
}>

packages/features/Segment.tsx

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
"use client";
22

3-
import { useCallback, useState } from "react";
4-
import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder";
5-
import type { ImmutableTree, BuilderProps } from "react-awesome-query-builder";
6-
import type { JsonTree } from "react-awesome-query-builder";
7-
83
import { buildStateFromQueryValue } from "@calcom/app-store/_utils/raqb/raqbUtils.client";
94
import {
10-
withRaqbSettingsAndWidgets,
115
ConfigFor,
6+
withRaqbSettingsAndWidgets,
127
} from "@calcom/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig";
138
import { getQueryBuilderConfigForAttributes } from "@calcom/app-store/routing-forms/lib/getQueryBuilderConfig";
149
import { useLocale } from "@calcom/lib/hooks/useLocale";
1510
import { isEqual } from "@calcom/lib/isEqual";
1611
import type { AttributesQueryValue } from "@calcom/lib/raqb/types";
17-
import { trpc, type RouterOutputs } from "@calcom/trpc/react";
12+
import { type RouterOutputs, trpc } from "@calcom/trpc/react";
1813
import cn from "@calcom/ui/classNames";
14+
import { useCallback, useState } from "react";
15+
import type { BuilderProps, ImmutableTree, JsonTree } from "react-awesome-query-builder";
16+
import { Builder, Utils as QbUtils, Query } from "react-awesome-query-builder";
1917

2018
export type Attributes = RouterOutputs["viewer"]["appRoutingForms"]["getAttributesForTeam"];
2119
export function useAttributes(teamId: number) {
@@ -34,12 +32,14 @@ function SegmentWithAttributes({
3432
queryValue: initialQueryValue,
3533
onQueryValueChange,
3634
className,
35+
filterMemberIds,
3736
}: {
3837
attributes: Attributes;
3938
teamId: number;
4039
queryValue: AttributesQueryValue | null;
4140
onQueryValueChange: ({ queryValue }: { queryValue: AttributesQueryValue }) => void;
4241
className?: string;
42+
filterMemberIds?: number[];
4343
}) {
4444
const attributesQueryBuilderConfig = getQueryBuilderConfigForAttributes({
4545
attributes,
@@ -91,7 +91,7 @@ function SegmentWithAttributes({
9191
/>
9292
</div>
9393
<div className="mt-4 text-sm">
94-
<MatchingTeamMembers teamId={teamId} queryValue={queryValue} />
94+
<MatchingTeamMembers teamId={teamId} queryValue={queryValue} filterMemberIds={filterMemberIds} />
9595
</div>
9696
</div>
9797
);
@@ -100,17 +100,19 @@ function SegmentWithAttributes({
100100
function MatchingTeamMembers({
101101
teamId,
102102
queryValue,
103+
filterMemberIds,
103104
}: {
104105
teamId: number;
105106
queryValue: AttributesQueryValue | null;
107+
filterMemberIds?: number[];
106108
}) {
107109
const { t } = useLocale();
108110

109111
// Check if queryValue has valid children properties value
110112
const hasValidValue = queryValue?.children1
111113
? Object.values(queryValue.children1).some(
112-
(child) => child.properties?.value?.[0] !== undefined && child.properties?.value?.[0] !== null
113-
)
114+
(child) => child.properties?.value?.[0] !== undefined && child.properties?.value?.[0] !== null
115+
)
114116
: false;
115117

116118
const { data: matchingTeamMembersWithResult, isPending } =
@@ -158,7 +160,11 @@ function MatchingTeamMembers({
158160
}
159161

160162
if (!matchingTeamMembersWithResult) return <span>{t("something_went_wrong")}</span>;
161-
const { result: matchingTeamMembers } = matchingTeamMembersWithResult;
163+
const { result: allMatchingTeamMembers } = matchingTeamMembersWithResult;
164+
165+
const matchingTeamMembers = filterMemberIds
166+
? allMatchingTeamMembers?.filter((member) => filterMemberIds.includes(member.id))
167+
: allMatchingTeamMembers;
162168

163169
return (
164170
<div className="border-subtle bg-cal-muted mt-4 stack-y-3 rounded-md border p-4">
@@ -184,11 +190,13 @@ export function Segment({
184190
queryValue,
185191
onQueryValueChange,
186192
className,
193+
filterMemberIds,
187194
}: {
188195
teamId: number;
189196
queryValue: AttributesQueryValue | null;
190197
onQueryValueChange: ({ queryValue }: { queryValue: AttributesQueryValue }) => void;
191198
className?: string;
199+
filterMemberIds?: number[];
192200
}) {
193201
const { attributes, isPending } = useAttributes(teamId);
194202
const { t } = useLocale();
@@ -205,6 +213,7 @@ export function Segment({
205213
queryValue={queryValue}
206214
onQueryValueChange={onQueryValueChange}
207215
className={className}
216+
filterMemberIds={filterMemberIds}
208217
/>
209218
);
210219
}

0 commit comments

Comments
 (0)