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