1
+ import React from 'react' ;
2
+ import { useDispatch , useSelector } from 'react-redux' ;
3
+ import styled from '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
+ import history from 'util/history' ;
18
+ import { Org } from 'constants/orgConstants' ;
19
+
20
+ // Styled Components
21
+ const WorkspaceSection = styled . div `
22
+ padding: 8px 0;
23
+ ` ;
24
+
25
+ const SectionHeader = 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
+ const SearchContainer = styled . div `
35
+ padding: 8px 12px;
36
+ border-bottom: 1px solid #f0f0f0;
37
+ ` ;
38
+
39
+ const StyledSearchInput = 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
+ const WorkspaceList = 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
+ const WorkspaceItem = 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
+ const WorkspaceName = 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
+ const ActiveIcon = styled ( CheckoutIcon ) `
97
+ width: 16px;
98
+ height: 16px;
99
+ color: #4965f2;
100
+ margin-left: 8px;
101
+ ` ;
102
+
103
+ const CreateWorkspaceItem = 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
+ const EmptyState = styled . div `
131
+ padding: 20px 16px;
132
+ text-align: center;
133
+ color: #8b8fa3;
134
+ font-size: 13px;
135
+ ` ;
136
+
137
+ const PaginationContainer = 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
+ const LoadingContainer = styled . div `
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ padding: 24px 16px;
173
+ ` ;
174
+
175
+ // Component Props
176
+ interface WorkspaceSectionProps {
177
+ user : User ;
178
+ isDropdownOpen : boolean ;
179
+ onClose : ( ) => void ;
180
+ }
181
+
182
+ // Main Component
183
+ export default function WorkspaceSectionComponent ( {
184
+ user,
185
+ isDropdownOpen,
186
+ onClose
187
+ } : WorkspaceSectionProps ) {
188
+ const dispatch = useDispatch ( ) ;
189
+ const sysConfig = 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 ) ) return null ;
205
+
206
+ // Event handlers
207
+ const handleOrgSwitch = ( orgId : string ) => {
208
+ if ( user . currentOrgId !== orgId ) {
209
+ dispatch ( switchOrg ( orgId ) ) ;
210
+ }
211
+ onClose ( ) ;
212
+ } ;
213
+
214
+ const handleCreateOrg = ( ) => {
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 = { < SearchIcon style = { { color : "#8b8fa3" } } /> }
231
+ size = "small"
232
+ />
233
+ </ SearchContainer >
234
+
235
+ { /* Workspace List */ }
236
+ < WorkspaceList >
237
+ { isLoading ? (
238
+ < LoadingContainer >
239
+ < Spin size = "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
+ < WorkspaceName title = { 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
+ < CreateWorkspaceItem onClick = { handleCreateOrg } >
283
+ < AddIcon />
284
+ { trans ( "profile.createOrg" ) }
285
+ </ CreateWorkspaceItem >
286
+ </ WorkspaceSection >
287
+ ) ;
288
+ }
0 commit comments