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