Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 28a2101

Browse files
committed
refactor profile dropdown
1 parent 9ea107b commit 28a2101

File tree

3 files changed

+500
-375
lines changed

3 files changed

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

0 commit comments

Comments
 (0)