Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit60e165c

Browse files
committed
fix: filter add group member by organization
This is accomplished by using the members endpoint instead of the usersendpoint, and to that end the UserAutocomplete component has beenreworked to support either endpoint as separate components with a sharedbase.
1 parent9f4f88f commit60e165c

File tree

4 files changed

+146
-51
lines changed

4 files changed

+146
-51
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{MockOrganizationMember}from"testHelpers/entities";
3+
import{MemberAutocomplete}from"./UserAutocomplete";
4+
5+
constmeta:Meta<typeofMemberAutocomplete>={
6+
title:"components/MemberAutocomplete",
7+
component:MemberAutocomplete,
8+
};
9+
10+
exportdefaultmeta;
11+
typeStory=StoryObj<typeofMemberAutocomplete>;
12+
13+
exportconstWithLabel:Story={
14+
args:{
15+
value:MockOrganizationMember,
16+
organizationId:MockOrganizationMember.organization_id,
17+
label:"Member",
18+
},
19+
};
20+
21+
exportconstNoLabel:Story={
22+
args:{
23+
value:MockOrganizationMember,
24+
organizationId:MockOrganizationMember.organization_id,
25+
},
26+
};

‎site/src/components/UserAutocomplete/UserAutocomplete.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const meta: Meta<typeof UserAutocomplete> = {
1010
exportdefaultmeta;
1111
typeStory=StoryObj<typeofUserAutocomplete>;
1212

13-
exportconstExample:Story={
13+
exportconstWithLabel:Story={
1414
args:{
1515
value:MockUser,
1616
label:"User",

‎site/src/components/UserAutocomplete/UserAutocomplete.tsx

Lines changed: 99 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { css } from "@emotion/css";
22
importAutocompletefrom"@mui/material/Autocomplete";
33
importCircularProgressfrom"@mui/material/CircularProgress";
44
importTextFieldfrom"@mui/material/TextField";
5+
import{getErrorMessage}from"api/errors";
6+
import{organizationMembers}from"api/queries/organizations";
57
import{users}from"api/queries/users";
6-
importtype{User}from"api/typesGenerated";
8+
importtype{OrganizationMemberWithUserData,User}from"api/typesGenerated";
79
import{Avatar}from"components/Avatar/Avatar";
810
import{AvatarData}from"components/AvatarData/AvatarData";
911
import{useDebouncedFunction}from"hooks/debounce";
@@ -16,71 +18,128 @@ import {
1618
import{useQuery}from"react-query";
1719
import{prepareQuery}from"utils/filters";
1820

19-
exporttypeUserAutocompleteProps={
20-
value:User|null;
21-
onChange:(user:User|null)=>void;
22-
label?:string;
21+
// The common properties between users and org members that we need.
22+
exporttypeSelectedUser={
23+
avatar_url:string;
24+
email:string;
25+
username:string;
26+
};
27+
28+
exporttypeCommonAutocompleteProps<TextendsSelectedUser>={
2329
className?:string;
24-
size?:ComponentProps<typeofTextField>["size"];
30+
label?:string;
31+
onChange:(user:T|null)=>void;
2532
required?:boolean;
33+
size?:ComponentProps<typeofTextField>["size"];
34+
value:T|null;
2635
};
2736

28-
exportconstUserAutocomplete:FC<UserAutocompleteProps>=({
29-
value,
30-
onChange,
31-
label,
32-
className,
33-
size="small",
34-
required,
35-
})=>{
36-
const[autoComplete,setAutoComplete]=useState<{
37-
value:string;
38-
open:boolean;
39-
}>({
40-
value:value?.email??"",
41-
open:false,
42-
});
37+
exporttypeUserAutocompleteProps=CommonAutocompleteProps<User>;
38+
39+
exportconstUserAutocomplete:FC<UserAutocompleteProps>=(props)=>{
40+
const[filter,setFilter]=useState<string>();
41+
4342
constusersQuery=useQuery({
4443
...users({
45-
q:prepareQuery(encodeURI(autoComplete.value)),
44+
q:filter!==undefined ?prepareQuery(encodeURI(filter)) :"",
4645
limit:25,
4746
}),
48-
enabled:autoComplete.open,
47+
enabled:filter!==undefined,
48+
keepPreviousData:true,
49+
});
50+
return(
51+
<InnerAutocomplete<User>
52+
error={usersQuery.error}
53+
isFetching={usersQuery.isFetching}
54+
setFilter={setFilter}
55+
users={usersQuery.data?.users}
56+
{...props}
57+
/>
58+
);
59+
};
60+
61+
exporttypeMemberAutocompleteProps=
62+
CommonAutocompleteProps<OrganizationMemberWithUserData>&{
63+
organizationId:string;
64+
};
65+
66+
exportconstMemberAutocomplete:FC<MemberAutocompleteProps>=({
67+
organizationId,
68+
...props
69+
})=>{
70+
const[filter,setFilter]=useState<string>();
71+
72+
// Currently this queries all members, as there is no pagination.
73+
constmembersQuery=useQuery({
74+
...organizationMembers(organizationId),
75+
enabled:filter!==undefined,
4976
keepPreviousData:true,
5077
});
78+
return(
79+
<InnerAutocomplete<OrganizationMemberWithUserData>
80+
error={membersQuery.error}
81+
isFetching={membersQuery.isFetching}
82+
setFilter={setFilter}
83+
users={membersQuery.data}
84+
{...props}
85+
/>
86+
);
87+
};
88+
89+
typeInnerAutocompleteProps<TextendsSelectedUser>=
90+
CommonAutocompleteProps<T>&{
91+
/** The error is null if not loaded or no error. */
92+
error:unknown;
93+
isFetching:boolean;
94+
/** Filter is undefined if the autocomplete is closed. */
95+
setFilter:(filter:string|undefined)=>void;
96+
/** Users are undefined if not loaded or errored. */
97+
users:readonlyT[]|undefined;
98+
};
99+
100+
constInnerAutocomplete=<TextendsSelectedUser>({
101+
className,
102+
error,
103+
isFetching,
104+
label,
105+
onChange,
106+
required,
107+
setFilter,
108+
size="small",
109+
users,
110+
value,
111+
}:InnerAutocompleteProps<T>)=>{
112+
const[open,setOpen]=useState(false);
51113

52114
const{debounced:debouncedInputOnChange}=useDebouncedFunction(
53115
(event:ChangeEvent<HTMLInputElement>)=>{
54-
setAutoComplete((state)=>({
55-
...state,
56-
value:event.target.value,
57-
}));
116+
setFilter(event.target.value??"");
58117
},
59118
750,
60119
);
61120

62121
return(
63122
<Autocomplete
64-
noOptionsText="No users found"
123+
noOptionsText={
124+
error
125+
?getErrorMessage(error,"Unable to fetch users")
126+
:"No users found"
127+
}
65128
className={className}
66-
options={usersQuery.data?.users??[]}
67-
loading={usersQuery.isLoading}
129+
options={users??[]}
130+
loading={!users&&!error}
68131
value={value}
69132
data-testid="user-autocomplete"
70-
open={autoComplete.open}
133+
open={open}
71134
isOptionEqualToValue={(a,b)=>a.username===b.username}
72135
getOptionLabel={(option)=>option.email}
73136
onOpen={()=>{
74-
setAutoComplete((state)=>({
75-
...state,
76-
open:true,
77-
}));
137+
setOpen(true);
138+
setFilter(value?.email??"");
78139
}}
79140
onClose={()=>{
80-
setAutoComplete({
81-
value:value?.email??"",
82-
open:false,
83-
});
141+
setOpen(false);
142+
setFilter(undefined);
84143
}}
85144
onChange={(_,newValue)=>{
86145
onChange(newValue);
@@ -117,9 +176,7 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
117176
),
118177
endAdornment:(
119178
<>
120-
{usersQuery.isFetching&&autoComplete.open&&(
121-
<CircularProgresssize={16}/>
122-
)}
179+
{isFetching&&open&&<CircularProgresssize={16}/>}
123180
{params.InputProps.endAdornment}
124181
</>
125182
),

‎site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import {
1818
groupPermissions,
1919
removeMember,
2020
}from"api/queries/groups";
21-
importtype{Group,ReducedUser,User}from"api/typesGenerated";
21+
importtype{
22+
Group,
23+
OrganizationMemberWithUserData,
24+
ReducedUser,
25+
}from"api/typesGenerated";
2226
import{ErrorAlert}from"components/Alert/ErrorAlert";
2327
import{AvatarData}from"components/AvatarData/AvatarData";
2428
import{DeleteDialog}from"components/Dialogs/DeleteDialog/DeleteDialog";
@@ -39,7 +43,7 @@ import {
3943
PaginationStatus,
4044
TableToolbar,
4145
}from"components/TableToolbar/TableToolbar";
42-
import{UserAutocomplete}from"components/UserAutocomplete/UserAutocomplete";
46+
import{MemberAutocomplete}from"components/UserAutocomplete/UserAutocomplete";
4347
import{UserAvatar}from"components/UserAvatar/UserAvatar";
4448
import{typeFC,useState}from"react";
4549
import{Helmet}from"react-helmet-async";
@@ -133,11 +137,12 @@ export const GroupPage: FC = () => {
133137
{canUpdateGroup&&groupData&&!isEveryoneGroup(groupData)&&(
134138
<AddGroupMember
135139
isLoading={addMemberMutation.isLoading}
136-
onSubmit={async(user,reset)=>{
140+
organizationId={groupData.organization_id}
141+
onSubmit={async(member,reset)=>{
137142
try{
138143
awaitaddMemberMutation.mutateAsync({
139144
groupId,
140-
userId:user.id,
145+
userId:member.user_id,
141146
});
142147
reset();
143148
awaitgroupQuery.refetch();
@@ -231,11 +236,17 @@ export const GroupPage: FC = () => {
231236

232237
interfaceAddGroupMemberProps{
233238
isLoading:boolean;
234-
onSubmit:(user:User,reset:()=>void)=>void;
239+
onSubmit:(user:OrganizationMemberWithUserData,reset:()=>void)=>void;
240+
organizationId:string;
235241
}
236242

237-
constAddGroupMember:FC<AddGroupMemberProps>=({ isLoading, onSubmit})=>{
238-
const[selectedUser,setSelectedUser]=useState<User|null>(null);
243+
constAddGroupMember:FC<AddGroupMemberProps>=({
244+
isLoading,
245+
onSubmit,
246+
organizationId,
247+
})=>{
248+
const[selectedUser,setSelectedUser]=
249+
useState<OrganizationMemberWithUserData|null>(null);
239250

240251
constresetValues=()=>{
241252
setSelectedUser(null);
@@ -252,9 +263,10 @@ const AddGroupMember: FC<AddGroupMemberProps> = ({ isLoading, onSubmit }) => {
252263
}}
253264
>
254265
<Stackdirection="row"alignItems="center"spacing={1}>
255-
<UserAutocomplete
266+
<MemberAutocomplete
256267
css={styles.autoComplete}
257268
value={selectedUser}
269+
organizationId={organizationId}
258270
onChange={(newValue)=>{
259271
setSelectedUser(newValue);
260272
}}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp