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