fix(team): select members for bulk edits

This commit is contained in:
mubashir1osmani 2026-07-11 15:35:17 -07:00
parent d252e91823
commit 62019c0cdd
3 changed files with 58 additions and 38 deletions

View file

@ -18,6 +18,7 @@ export interface MemberTableProps {
extraColumns?: ColumnsType<Member>;
showDeleteForMember?: (member: Member) => boolean;
emptyText?: string;
rowSelection?: React.ComponentProps<typeof Table<Member>>["rowSelection"];
}
export default function MemberTable({
@ -31,6 +32,7 @@ export default function MemberTable({
extraColumns = [],
showDeleteForMember,
emptyText,
rowSelection,
}: MemberTableProps) {
const baseColumns: ColumnsType<Member> = [
{
@ -106,6 +108,7 @@ export default function MemberTable({
<Table
columns={baseColumns}
dataSource={members}
rowSelection={rowSelection}
rowKey={(record) => record.user_id ?? record.user_email ?? JSON.stringify(record)}
pagination={false}
size="small"

View file

@ -182,6 +182,25 @@ describe("TeamMembersComponent", () => {
expect(screen.getByText("Add Member")).toBeInTheDocument();
});
it("should show checkboxes after Select Members is clicked", async () => {
const user = userEvent.setup();
renderWithProviders(
<TeamMembersComponent
teamData={createMockTeamData()}
canEditTeam={true}
handleMemberDelete={mockHandleMemberDelete}
setSelectedEditMember={mockSetSelectedEditMember}
setIsEditMemberModalVisible={mockSetIsEditMemberModalVisible}
setIsAddMemberModalVisible={mockSetIsAddMemberModalVisible}
/>,
);
await user.click(screen.getByRole("button", { name: "Select Members" }));
expect(screen.getByRole("button", { name: "Bulk Edit (0 selected)" })).toBeDisabled();
expect(screen.getAllByRole("checkbox")).toHaveLength(3);
});
it("should display dash when user email is null", () => {
renderWithProviders(
<TeamMembersComponent

View file

@ -35,6 +35,8 @@ export default function TeamMemberTab({
}: TeamMemberTabProps) {
const [isBulkUpdateVisible, setIsBulkUpdateVisible] = useState(false);
const [isBulkUpdating, setIsBulkUpdating] = useState(false);
const [selectionMode, setSelectionMode] = useState(false);
const [selectedMembers, setSelectedMembers] = useState<Member[]>([]);
const [bulkUpdateForm] = Form.useForm();
const formatNumber = (value: number | null): string => {
if (value === null || value === undefined) return "0";
@ -193,8 +195,6 @@ export default function TeamMemberTab({
];
const handleBulkUpdate = async (values: {
all_members_in_team?: boolean;
user_ids?: string[];
apply_role?: boolean;
role?: "admin" | "user";
apply_max_budget?: boolean;
@ -209,6 +209,11 @@ export default function TeamMemberTab({
allowed_models?: string[];
}) => {
if (!accessToken) return;
const userIds = selectedMembers.flatMap((member) => (member.user_id ? [member.user_id] : []));
if (userIds.length === 0) {
NotificationsManager.fromBackend("Select at least one team member");
return;
}
const updateFields: TeamMemberBulkUpdateFields = {
...(values.apply_role ? { role: values.role } : {}),
...(values.apply_max_budget ? { max_budget_in_team: values.max_budget_in_team ?? null } : {}),
@ -224,15 +229,11 @@ export default function TeamMemberTab({
setIsBulkUpdating(true);
try {
const response = await teamMemberBulkUpdateCall(
accessToken,
teamData.team_id,
values.user_ids ?? [],
values.all_members_in_team === true,
updateFields,
);
const response = await teamMemberBulkUpdateCall(accessToken, teamData.team_id, userIds, false, updateFields);
await onMembersUpdated?.();
setIsBulkUpdateVisible(false);
setSelectedMembers([]);
setSelectionMode(false);
bulkUpdateForm.resetFields();
NotificationsManager.success(
`${response.successful_updates.length} team member${response.successful_updates.length === 1 ? "" : "s"} updated`,
@ -250,9 +251,22 @@ export default function TeamMemberTab({
return (
<>
{canEditTeam && (
<Button className="mb-3" onClick={() => setIsBulkUpdateVisible(true)}>
Bulk Update Members
</Button>
<Space className="mb-3">
<Button
onClick={() => {
setSelectionMode((current) => !current);
setSelectedMembers([]);
}}
type={selectionMode ? "primary" : "default"}
>
{selectionMode ? "Cancel Selection" : "Select Members"}
</Button>
{selectionMode && (
<Button type="primary" disabled={selectedMembers.length === 0} onClick={() => setIsBulkUpdateVisible(true)}>
Bulk Edit ({selectedMembers.length} selected)
</Button>
)}
</Space>
)}
<MemberTable
members={teamData.team_info.members_with_roles}
@ -275,12 +289,21 @@ export default function TeamMemberTab({
roleColumnTitle="Team Role"
roleTooltip="This role applies only to this team and is independent from the user's proxy-level role."
extraColumns={extraColumns}
rowSelection={
selectionMode
? {
selectedRowKeys: selectedMembers.flatMap((member) => (member.user_id ? [member.user_id] : [])),
onChange: (_selectedRowKeys, selectedRows) => setSelectedMembers(selectedRows),
getCheckboxProps: (member) => ({ disabled: member.user_id === null }),
}
: undefined
}
showDeleteForMember={() =>
isProxyAdmin || (canEditTeam && !isUserTeamAdmin) || (isUserTeamAdmin && !disableTeamAdminDeleteTeamUser)
}
/>
<Modal
title="Bulk Update Team Members"
title={`Bulk Edit ${selectedMembers.length} Team Member${selectedMembers.length === 1 ? "" : "s"}`}
open={isBulkUpdateVisible}
onCancel={() => setIsBulkUpdateVisible(false)}
onOk={() => bulkUpdateForm.submit()}
@ -288,31 +311,6 @@ export default function TeamMemberTab({
confirmLoading={isBulkUpdating}
>
<Form form={bulkUpdateForm} layout="vertical" onFinish={handleBulkUpdate}>
<Form.Item name="all_members_in_team" valuePropName="checked">
<Checkbox>Update all {teamData.team_info.members_with_roles.length} members</Checkbox>
</Form.Item>
<Form.Item noStyle shouldUpdate>
{({ getFieldValue }) =>
!getFieldValue("all_members_in_team") && (
<Form.Item
name="user_ids"
label="Members"
rules={[{ required: true, message: "Select at least one member" }]}
>
<Select
mode="multiple"
placeholder="Select members"
options={teamData.team_info.members_with_roles
.filter((member) => member.user_id)
.map((member) => ({
label: member.user_email || member.user_id,
value: member.user_id as string,
}))}
/>
</Form.Item>
)
}
</Form.Item>
<Typography.Text type="secondary">Choose the fields to apply to every selected member.</Typography.Text>
<Form.Item name="apply_role" valuePropName="checked" className="mt-3 mb-1">
<Checkbox>Team role</Checkbox>