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

Commit524bc75

Browse files
Merge pull request#1787 from iamfaran/feat/myorg-endpoint
[Feat]: Improve Profile Dropdown, and Workspaces Page using "myorg" endpoint
2 parentsfaffd94 +24e04c0 commit524bc75

File tree

12 files changed

+990
-329
lines changed

12 files changed

+990
-329
lines changed

‎client/packages/lowcoder/src/api/userApi.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
importApifrom"api/api";
22
import{AxiosPromise}from"axios";
3-
import{OrgAndRole}from"constants/orgConstants";
3+
import{Org,OrgAndRole}from"constants/orgConstants";
44
import{BaseUserInfo,CurrentUser}from"constants/userConstants";
55
import{MarkUserStatusPayload,UpdateUserPayload}from"redux/reduxActions/userActions";
66
import{ApiResponse,GenericApiResponse}from"./apiResponses";
@@ -60,10 +60,23 @@ export interface FetchApiKeysResponse extends ApiResponse {
6060

6161
exporttypeGetCurrentUserResponse=GenericApiResponse<CurrentUser>;
6262

63+
exportinterfaceGetMyOrgsResponseextendsApiResponse{
64+
data:{
65+
data:Array<{
66+
orgId:string;
67+
orgName:string;
68+
}>;
69+
pageNum:number;
70+
pageSize:number;
71+
total:number;
72+
};
73+
}
74+
6375
classUserApiextendsApi{
6476
staticthirdPartyLoginURL="/auth/tp/login";
6577
staticthirdPartyBindURL="/auth/tp/bind";
6678
staticusersURL="/users";
79+
staticmyOrgsURL="/users/myorg";
6780
staticsendVerifyCodeURL="/auth/otp/send";
6881
staticlogoutURL="/auth/logout";
6982
staticuserURL="/users/me";
@@ -127,6 +140,19 @@ class UserApi extends Api {
127140
staticgetCurrentUser():AxiosPromise<GetCurrentUserResponse>{
128141
returnApi.get(UserApi.currentUserURL);
129142
}
143+
staticgetMyOrgs(
144+
pageNum:number=1,
145+
pageSize:number=20,
146+
orgName?:string
147+
):AxiosPromise<GetMyOrgsResponse>{
148+
constparams=newURLSearchParams({
149+
pageNum:pageNum.toString(),
150+
pageSize:pageSize.toString(),
151+
...(orgName&&{ orgName})
152+
});
153+
154+
returnApi.get(`${UserApi.myOrgsURL}?${params}`);
155+
}
130156

131157
staticgetRawCurrentUser():AxiosPromise<GetCurrentUserResponse>{
132158
returnApi.get(UserApi.rawCurrentUserURL);

‎client/packages/lowcoder/src/constants/reduxActionConstants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export const ReduxActionTypes = {
1111
FETCH_API_KEYS_SUCCESS:"FETCH_API_KEYS_SUCCESS",
1212
MOVE_TO_FOLDER2_SUCCESS:"MOVE_TO_FOLDER2_SUCCESS",
1313

14+
/* workspace RELATED */
15+
FETCH_WORKSPACES_INIT:"FETCH_WORKSPACES_INIT",
16+
FETCH_WORKSPACES_SUCCESS:"FETCH_WORKSPACES_SUCCESS",
17+
18+
19+
1420
/* plugin RELATED */
1521
FETCH_DATA_SOURCE_TYPES:"FETCH_DATA_SOURCE_TYPES",
1622
FETCH_DATA_SOURCE_TYPES_SUCCESS:"FETCH_DATA_SOURCE_TYPES_SUCCESS",

‎client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3639,6 +3639,7 @@ export const en = {
36393639
"profile":{
36403640
"orgSettings":"Workspace Settings",
36413641
"switchOrg":"Switch Workspace",
3642+
"switchWorkspace":"Switch",
36423643
"joinedOrg":"My Workspaces",
36433644
"createOrg":"Create Workspace",
36443645
"logout":"Log Out",
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
importReactfrom'react';
2+
import{useDispatch,useSelector}from'react-redux';
3+
importstyledfrom'styled-components';
4+
import{Input,Pagination,Spin}from'antd';
5+
import{User}from'constants/userConstants';
6+
import{switchOrg,createOrgAction}from'redux/reduxActions/orgActions';
7+
import{selectSystemConfig}from'redux/selectors/configSelectors';
8+
import{showSwitchOrg}from'@lowcoder-ee/pages/common/customerService';
9+
import{useWorkspaceManager}from'util/useWorkspaceManager';
10+
import{trans}from'i18n';
11+
import{
12+
AddIcon,
13+
CheckoutIcon,
14+
SearchIcon,
15+
}from'lowcoder-design';
16+
import{ORGANIZATION_SETTING}from'constants/routesURL';
17+
importhistoryfrom'util/history';
18+
import{Org}from'constants/orgConstants';
19+
20+
// Styled Components
21+
constWorkspaceSection=styled.div`
22+
padding: 8px 0;
23+
`;
24+
25+
constSectionHeader=styled.div`
26+
padding: 8px 16px;
27+
font-size: 12px;
28+
font-weight: 500;
29+
color: #8b8fa3;
30+
text-transform: uppercase;
31+
letter-spacing: 0.5px;
32+
`;
33+
34+
constSearchContainer=styled.div`
35+
padding: 8px 12px;
36+
border-bottom: 1px solid #f0f0f0;
37+
`;
38+
39+
constStyledSearchInput=styled(Input)`
40+
.ant-input {
41+
border: 1px solid #e1e3eb;
42+
border-radius: 6px;
43+
font-size: 13px;
44+
45+
&:focus {
46+
border-color: #4965f2;
47+
box-shadow: 0 0 0 2px rgba(73, 101, 242, 0.1);
48+
}
49+
}
50+
`;
51+
52+
constWorkspaceList=styled.div`
53+
max-height: 200px;
54+
overflow-y: auto;
55+
56+
&::-webkit-scrollbar {
57+
width: 4px;
58+
}
59+
60+
&::-webkit-scrollbar-track {
61+
background: #f1f1f1;
62+
}
63+
64+
&::-webkit-scrollbar-thumb {
65+
background: #c1c1c1;
66+
border-radius: 2px;
67+
}
68+
69+
&::-webkit-scrollbar-thumb:hover {
70+
background: #a8a8a8;
71+
}
72+
`;
73+
74+
constWorkspaceItem=styled.div<{isActive?:boolean}>`
75+
display: flex;
76+
align-items: center;
77+
padding: 10px 16px;
78+
cursor: pointer;
79+
transition: background-color 0.2s;
80+
background-color:${props=>props.isActive ?'#f0f5ff' :'transparent'};
81+
82+
&:hover {
83+
background-color:${props=>props.isActive ?'#f0f5ff' :'#f8f9fa'};
84+
}
85+
`;
86+
87+
constWorkspaceName=styled.div`
88+
flex: 1;
89+
font-size: 13px;
90+
color: #222222;
91+
overflow: hidden;
92+
text-overflow: ellipsis;
93+
white-space: nowrap;
94+
`;
95+
96+
constActiveIcon=styled(CheckoutIcon)`
97+
width: 16px;
98+
height: 16px;
99+
color: #4965f2;
100+
margin-left: 8px;
101+
`;
102+
103+
constCreateWorkspaceItem=styled.div`
104+
display: flex;
105+
align-items: center;
106+
padding: 12px 16px;
107+
cursor: pointer;
108+
transition: background-color 0.2s;
109+
font-size: 13px;
110+
color: #4965f2;
111+
font-weight: 500;
112+
113+
&:hover {
114+
background-color: #f0f5ff;
115+
color: #3651d4;
116+
}
117+
118+
svg {
119+
width: 16px;
120+
height: 16px;
121+
margin-right: 10px;
122+
color: #4965f2;
123+
}
124+
125+
&:hover svg {
126+
color: #3651d4;
127+
}
128+
`;
129+
130+
constEmptyState=styled.div`
131+
padding: 20px 16px;
132+
text-align: center;
133+
color: #8b8fa3;
134+
font-size: 13px;
135+
`;
136+
137+
constPaginationContainer=styled.div`
138+
padding: 12px 16px;
139+
border-top: 1px solid #f0f0f0;
140+
display: flex;
141+
justify-content: center;
142+
143+
.ant-pagination {
144+
margin: 0;
145+
146+
.ant-pagination-item {
147+
min-width: 24px;
148+
height: 24px;
149+
line-height: 22px;
150+
font-size: 12px;
151+
margin-right: 4px;
152+
}
153+
154+
.ant-pagination-prev,
155+
.ant-pagination-next {
156+
min-width: 24px;
157+
height: 24px;
158+
line-height: 22px;
159+
margin-right: 4px;
160+
}
161+
162+
.ant-pagination-item-link {
163+
font-size: 11px;
164+
}
165+
}
166+
`;
167+
168+
constLoadingContainer=styled.div`
169+
display: flex;
170+
align-items: center;
171+
justify-content: center;
172+
padding: 24px 16px;
173+
`;
174+
175+
// Component Props
176+
interfaceWorkspaceSectionProps{
177+
user:User;
178+
isDropdownOpen:boolean;
179+
onClose:()=>void;
180+
}
181+
182+
// Main Component
183+
exportdefaultfunctionWorkspaceSectionComponent({
184+
user,
185+
isDropdownOpen,
186+
onClose
187+
}:WorkspaceSectionProps){
188+
constdispatch=useDispatch();
189+
constsysConfig=useSelector(selectSystemConfig);
190+
191+
// Use our custom hook
192+
const{
193+
searchTerm,
194+
currentPage,
195+
totalCount,
196+
isLoading,
197+
displayWorkspaces,
198+
handleSearchChange,
199+
handlePageChange,
200+
pageSize,
201+
}=useWorkspaceManager({});
202+
203+
// Early returns for better performance
204+
if(!showSwitchOrg(user,sysConfig))returnnull;
205+
206+
// Event handlers
207+
consthandleOrgSwitch=(orgId:string)=>{
208+
if(user.currentOrgId!==orgId){
209+
dispatch(switchOrg(orgId));
210+
}
211+
onClose();
212+
};
213+
214+
consthandleCreateOrg=()=>{
215+
dispatch(createOrgAction(user.orgs));
216+
history.push(ORGANIZATION_SETTING);
217+
onClose();
218+
};
219+
220+
return(
221+
<WorkspaceSection>
222+
<SectionHeader>{trans("profile.switchOrg")}</SectionHeader>
223+
224+
{/* Search Input - Only show if more than 3 workspaces */}
225+
<SearchContainer>
226+
<StyledSearchInput
227+
placeholder="Search workspaces..."
228+
value={searchTerm}
229+
onChange={(e)=>handleSearchChange(e.target.value)}
230+
prefix={<SearchIconstyle={{color:"#8b8fa3"}}/>}
231+
size="small"
232+
/>
233+
</SearchContainer>
234+
235+
{/* Workspace List */}
236+
<WorkspaceList>
237+
{isLoading ?(
238+
<LoadingContainer>
239+
<Spinsize="small"/>
240+
</LoadingContainer>
241+
) :displayWorkspaces.length>0 ?(
242+
displayWorkspaces.map((org:Org)=>(
243+
<WorkspaceItem
244+
key={org.id}
245+
isActive={user.currentOrgId===org.id}
246+
onClick={()=>handleOrgSwitch(org.id)}
247+
>
248+
<WorkspaceNametitle={org.name}>{org.name}</WorkspaceName>
249+
{user.currentOrgId===org.id&&<ActiveIcon/>}
250+
</WorkspaceItem>
251+
))
252+
) :(
253+
<EmptyState>
254+
{searchTerm.trim()
255+
?"No workspaces found"
256+
:"No workspaces available"
257+
}
258+
</EmptyState>
259+
)}
260+
</WorkspaceList>
261+
262+
{/* Pagination - Only show when needed */}
263+
{totalCount>pageSize&&!isLoading&&(
264+
<PaginationContainer>
265+
<Pagination
266+
current={currentPage}
267+
total={totalCount}
268+
pageSize={pageSize}
269+
size="small"
270+
showSizeChanger={false}
271+
showQuickJumper={false}
272+
showTotal={(total,range)=>
273+
`${range[0]}-${range[1]} of${total}`
274+
}
275+
onChange={handlePageChange}
276+
simple={totalCount>100}// Simple mode for large datasets
277+
/>
278+
</PaginationContainer>
279+
)}
280+
281+
{/* Create Workspace Button */}
282+
<CreateWorkspaceItemonClick={handleCreateOrg}>
283+
<AddIcon/>
284+
{trans("profile.createOrg")}
285+
</CreateWorkspaceItem>
286+
</WorkspaceSection>
287+
);
288+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp