1
1
import React , { useState , useEffect } from 'react' ;
2
- import { Card , Button , Divider , Alert , message , Table , Tag , Input , Space } from 'antd' ;
3
- import { SyncOutlined , TeamOutlined } from '@ant-design/icons' ;
2
+ import { Card , Button , Alert , message , Table , Tag , Input , Space , Row , Col , Avatar , Tooltip } from 'antd' ;
3
+ import { SyncOutlined , TeamOutlined , UserOutlined , UsergroupAddOutlined , SettingOutlined , CodeOutlined } from '@ant-design/icons' ;
4
4
import Title from 'antd/lib/typography/Title' ;
5
5
import { Environment } from '../types/environment.types' ;
6
6
import { UserGroup , UserGroupsTabStats } from '../types/userGroup.types' ;
@@ -89,91 +89,166 @@ const UserGroupsTab: React.FC<UserGroupsTabProps> = ({ environment }) => {
89
89
group . groupId . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) )
90
90
: userGroups ;
91
91
92
+ // Helper function to generate colors from strings
93
+ const stringToColor = ( str : string ) => {
94
+ let hash = 0 ;
95
+ for ( let i = 0 ; i < str . length ; i ++ ) {
96
+ hash = str . charCodeAt ( i ) + ( ( hash << 5 ) - hash ) ;
97
+ }
98
+
99
+ const hue = Math . abs ( hash % 360 ) ;
100
+ return `hsl(${ hue } , 70%, 50%)` ;
101
+ } ;
102
+
103
+ // Stat card component
104
+ const StatCard = ( { title, value, icon } : { title : string ; value : number ; icon : React . ReactNode } ) => (
105
+ < Card
106
+ style = { {
107
+ height : '100%' ,
108
+ borderRadius : '8px' ,
109
+ boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
110
+ } }
111
+ >
112
+ < div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' } } >
113
+ < div >
114
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginBottom : '8px' } } > { title } </ div >
115
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { value } </ div >
116
+ </ div >
117
+ < div style = { {
118
+ fontSize : '28px' ,
119
+ opacity : 0.8 ,
120
+ color : '#722ed1' ,
121
+ padding : '12px' ,
122
+ backgroundColor : 'rgba(114, 46, 209, 0.1)' ,
123
+ borderRadius : '50%' ,
124
+ display : 'flex' ,
125
+ alignItems : 'center' ,
126
+ justifyContent : 'center'
127
+ } } >
128
+ { icon }
129
+ </ div >
130
+ </ div >
131
+ </ Card >
132
+ ) ;
133
+
92
134
// Table columns
93
135
const columns = [
94
136
{
95
- title : 'Name' ,
96
- dataIndex : 'groupName' ,
97
- key : 'groupName' ,
98
- render : ( text : string ) => < span className = "group-name" > { text } </ span >
99
- } ,
100
- {
101
- title : 'ID' ,
102
- dataIndex : 'groupId' ,
103
- key : 'groupId' ,
104
- ellipsis : true ,
137
+ title : 'User Group' ,
138
+ key : 'group' ,
139
+ render : ( group : UserGroup ) => (
140
+ < div style = { { display : 'flex' , alignItems : 'center' } } >
141
+ < Avatar
142
+ style = { {
143
+ backgroundColor : stringToColor ( group . groupName ) ,
144
+ marginRight : 12
145
+ } }
146
+ shape = "square"
147
+ >
148
+ { group . groupName . charAt ( 0 ) . toUpperCase ( ) }
149
+ </ Avatar >
150
+ < div >
151
+ < div style = { { fontWeight : 500 } } > { group . groupName } </ div >
152
+ < div style = { { fontSize : 12 , color : '#8c8c8c' , marginTop : 4 } } >
153
+ { group . groupId }
154
+ </ div >
155
+ </ div >
156
+ </ div >
157
+ ) ,
105
158
} ,
106
159
{
107
160
title : 'Type' ,
108
161
key : 'type' ,
109
162
render : ( _ : any , group : UserGroup ) => {
110
- if ( group . allUsersGroup ) return < Tag color = "blue" > All Users</ Tag > ;
111
- if ( group . devGroup ) return < Tag color = "purple" > Developers</ Tag > ;
112
- return < Tag color = "default" > Custom</ Tag > ;
163
+ if ( group . allUsersGroup ) return (
164
+ < Tag color = "blue" style = { { borderRadius : '12px' } } >
165
+ < UserOutlined style = { { marginRight : 4 } } /> All Users
166
+ </ Tag >
167
+ ) ;
168
+ if ( group . devGroup ) return (
169
+ < Tag color = "purple" style = { { borderRadius : '12px' } } >
170
+ < CodeOutlined style = { { marginRight : 4 } } /> Developers
171
+ </ Tag >
172
+ ) ;
173
+ return (
174
+ < Tag color = "default" style = { { borderRadius : '12px' } } >
175
+ < SettingOutlined style = { { marginRight : 4 } } /> Custom
176
+ </ Tag >
177
+ ) ;
113
178
} ,
114
179
} ,
115
180
{
116
181
title : 'Members' ,
117
182
key : 'members' ,
118
- render : ( _ : any , group : UserGroup ) => group . stats ?. userCount || 0 ,
183
+ render : ( _ : any , group : UserGroup ) => (
184
+ < Tooltip title = "Total number of members in this group" >
185
+ < Tag style = { { borderRadius : '12px' , backgroundColor : '#f6f6f6' , color : '#333' } } >
186
+ < UserOutlined style = { { marginRight : 4 } } /> { group . stats ?. userCount || 0 }
187
+ </ Tag >
188
+ </ Tooltip >
189
+ ) ,
119
190
} ,
120
191
{
121
192
title : 'Admin Members' ,
122
193
key : 'adminMembers' ,
123
- render : ( _ : any , group : UserGroup ) => group . stats ?. adminUserCount || 0 ,
194
+ render : ( _ : any , group : UserGroup ) => (
195
+ < Tooltip title = "Number of admin users in this group" >
196
+ < Tag style = { { borderRadius : '12px' , backgroundColor : '#fff1f0' , color : '#cf1322' } } >
197
+ < UserOutlined style = { { marginRight : 4 } } /> { group . stats ?. adminUserCount || 0 }
198
+ </ Tag >
199
+ </ Tooltip >
200
+ ) ,
124
201
} ,
125
202
{
126
203
title : 'Created' ,
127
204
dataIndex : 'createTime' ,
128
205
key : 'createTime' ,
129
- render : ( createTime : number ) => new Date ( createTime ) . toLocaleDateString ( ) ,
206
+ render : ( createTime : number ) => (
207
+ < span style = { { color : '#8c8c8c' } } >
208
+ { new Date ( createTime ) . toLocaleDateString ( ) }
209
+ </ span >
210
+ ) ,
130
211
}
131
212
] ;
132
213
133
214
return (
134
- < Card >
135
- { /* Header with refresh button */ }
136
- < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "center" , marginBottom : "16px" } } >
137
- < Title level = { 5 } > User Groups in this Environment</ Title >
215
+ < div style = { { padding : '16px 0' } } >
216
+ { /* Header */ }
217
+ < div style = { {
218
+ display : "flex" ,
219
+ justifyContent : "space-between" ,
220
+ alignItems : "center" ,
221
+ marginBottom : "24px" ,
222
+ background : 'linear-gradient(135deg, #722ed1 0%, #eb2f96 100%)' ,
223
+ padding : '20px 24px' ,
224
+ borderRadius : '8px' ,
225
+ color : 'white'
226
+ } } >
227
+ < div >
228
+ < Title level = { 4 } style = { { color : 'white' , margin : 0 } } >
229
+ < UsergroupAddOutlined style = { { marginRight : 10 } } /> User Groups
230
+ </ Title >
231
+ < p style = { { marginBottom : 0 } } > Manage user groups in this environment</ p >
232
+ </ div >
138
233
< Button
139
234
icon = { < SyncOutlined spin = { refreshing } /> }
140
235
onClick = { handleRefresh }
141
236
loading = { loading }
237
+ type = "primary"
238
+ ghost
142
239
>
143
240
Refresh
144
241
</ Button >
145
242
</ div >
146
243
147
- { /* Stats display */ }
148
- < div style = { { display : 'flex' , flexWrap : 'wrap' , gap : '24px' , marginBottom : '16px' } } >
149
- < div >
150
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Total Groups</ div >
151
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . total } </ div >
152
- </ div >
153
- < div >
154
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > All Users Groups</ div >
155
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . allUsers } </ div >
156
- </ div >
157
- < div >
158
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Developer Groups</ div >
159
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . developers } </ div >
160
- </ div >
161
- < div >
162
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Custom Groups</ div >
163
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . custom } </ div >
164
- </ div >
165
- </ div >
166
-
167
- < Divider style = { { margin : "16px 0" } } />
168
-
169
244
{ /* Error display */ }
170
245
{ error && (
171
246
< Alert
172
247
message = "Error loading user groups"
173
248
description = { error }
174
249
type = "error"
175
250
showIcon
176
- style = { { marginBottom : "16px " } }
251
+ style = { { marginBottom : "20px " } }
177
252
/>
178
253
) }
179
254
@@ -184,49 +259,95 @@ const UserGroupsTab: React.FC<UserGroupsTabProps> = ({ environment }) => {
184
259
description = "Missing required configuration: API key or API service URL"
185
260
type = "warning"
186
261
showIcon
187
- style = { { marginBottom : "16px " } }
262
+ style = { { marginBottom : "20px " } }
188
263
/>
189
264
) }
190
265
266
+ { /* Stats display */ }
267
+ < Row gutter = { [ 16 , 16 ] } style = { { marginBottom : '24px' } } >
268
+ < Col xs = { 24 } sm = { 12 } md = { 6 } >
269
+ < StatCard
270
+ title = "Total Groups"
271
+ value = { stats . total }
272
+ icon = { < TeamOutlined /> }
273
+ />
274
+ </ Col >
275
+ < Col xs = { 24 } sm = { 12 } md = { 6 } >
276
+ < StatCard
277
+ title = "All Users Groups"
278
+ value = { stats . allUsers }
279
+ icon = { < UserOutlined /> }
280
+ />
281
+ </ Col >
282
+ < Col xs = { 24 } sm = { 12 } md = { 6 } >
283
+ < StatCard
284
+ title = "Developer Groups"
285
+ value = { stats . developers }
286
+ icon = { < CodeOutlined /> }
287
+ />
288
+ </ Col >
289
+ < Col xs = { 24 } sm = { 12 } md = { 6 } >
290
+ < StatCard
291
+ title = "Custom Groups"
292
+ value = { stats . custom }
293
+ icon = { < SettingOutlined /> }
294
+ />
295
+ </ Col >
296
+ </ Row >
297
+
191
298
{ /* Content */ }
192
- { loading ? (
193
- < div style = { { display : 'flex' , justifyContent : 'center' , padding : '20px' } } >
194
- < Spin tip = "Loading user groups..." />
195
- </ div >
196
- ) : userGroups . length === 0 ? (
197
- < Empty
198
- description = { error || "No user groups found in this environment" }
199
- image = { Empty . PRESENTED_IMAGE_SIMPLE }
200
- />
201
- ) : (
202
- < >
203
- { /* Search Bar */ }
204
- < div style = { { marginBottom : 16 } } >
205
- < Search
206
- placeholder = "Search user groups by name or ID"
207
- allowClear
208
- onSearch = { value => setSearchText ( value ) }
209
- onChange = { e => setSearchText ( e . target . value ) }
210
- style = { { width : 300 } }
211
- />
212
- { searchText && filteredUserGroups . length !== userGroups . length && (
213
- < div style = { { marginTop : 8 } } >
214
- Showing { filteredUserGroups . length } of { userGroups . length } user groups
215
- </ div >
216
- ) }
299
+ < Card
300
+ style = { {
301
+ borderRadius : '8px' ,
302
+ boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
303
+ } }
304
+ >
305
+ { loading ? (
306
+ < div style = { { display : 'flex' , justifyContent : 'center' , padding : '40px' } } >
307
+ < Spin size = "large" tip = "Loading user groups..." />
217
308
</ div >
218
-
219
- < Table
220
- columns = { columns }
221
- dataSource = { filteredUserGroups }
222
- rowKey = "groupId"
223
- pagination = { { pageSize : 10 } }
224
- size = "middle"
225
- scroll = { { x : 'max-content' } }
309
+ ) : userGroups . length === 0 ? (
310
+ < Empty
311
+ description = { error || "No user groups found in this environment" }
312
+ image = { Empty . PRESENTED_IMAGE_SIMPLE }
226
313
/>
227
- </ >
228
- ) }
229
- </ Card >
314
+ ) : (
315
+ < >
316
+ { /* Search Bar */ }
317
+ < div style = { { marginBottom : 20 } } >
318
+ < Search
319
+ placeholder = "Search user groups by name or ID"
320
+ allowClear
321
+ onSearch = { value => setSearchText ( value ) }
322
+ onChange = { e => setSearchText ( e . target . value ) }
323
+ style = { { width : 300 } }
324
+ size = "large"
325
+ />
326
+ { searchText && filteredUserGroups . length !== userGroups . length && (
327
+ < div style = { { marginTop : 8 , color : '#8c8c8c' } } >
328
+ Showing { filteredUserGroups . length } of { userGroups . length } user groups
329
+ </ div >
330
+ ) }
331
+ </ div >
332
+
333
+ < Table
334
+ columns = { columns }
335
+ dataSource = { filteredUserGroups }
336
+ rowKey = "groupId"
337
+ pagination = { {
338
+ pageSize : 10 ,
339
+ showTotal : ( total , range ) => `${ range [ 0 ] } -${ range [ 1 ] } of ${ total } user groups`
340
+ } }
341
+ style = { {
342
+ borderRadius : '8px' ,
343
+ overflow : 'hidden'
344
+ } }
345
+ rowClassName = { ( ) => 'group-row' }
346
+ />
347
+ </ >
348
+ ) }
349
+ </ Card >
350
+ </ div >
230
351
) ;
231
352
} ;
232
353
0 commit comments