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' ;
4+ import Title from 'antd/lib/typography/Title' ;
5+ import { Environment } from '../types/environment.types' ;
6+ import { UserGroup } from '../types/userGroup.types' ;
7+ import { getEnvironmentUserGroups } from '../services/environments.service' ;
8+ import { Spin , Empty } from 'antd' ;
9+
10+ const { Search } = Input ;
11+
12+ interface UserGroupsTabProps {
13+ environment : Environment ;
14+ }
15+
16+ const UserGroupsTab : React . FC < UserGroupsTabProps > = ( { environment } ) => {
17+ const [ userGroups , setUserGroups ] = useState < UserGroup [ ] > ( [ ] ) ;
18+ const [ stats , setStats ] = useState ( {
19+ total : 0 ,
20+ active : 0 ,
21+ inactive : 0
22+ } ) ;
23+ const [ loading , setLoading ] = useState ( false ) ;
24+ const [ refreshing , setRefreshing ] = useState ( false ) ;
25+ const [ error , setError ] = useState < string | null > ( null ) ;
26+ const [ searchText , setSearchText ] = useState ( '' ) ;
27+
28+ // Fetch user groups
29+ const fetchUserGroups = async ( ) => {
30+ if ( ! environment ) return ;
31+
32+ setLoading ( true ) ;
33+ setError ( null ) ;
34+
35+ try {
36+ // Check for required environment properties
37+ if ( ! environment . environmentApikey || ! environment . environmentApiServiceUrl ) {
38+ setError ( 'Missing required configuration: API key or API service URL' ) ;
39+ setLoading ( false ) ;
40+ return ;
41+ }
42+
43+ const groups = await getEnvironmentUserGroups (
44+ environment . environmentId ,
45+ environment . environmentApikey ,
46+ environment . environmentApiServiceUrl
47+ ) ;
48+
49+ setUserGroups ( groups ) ;
50+
51+ // Calculate stats
52+ const total = groups . length ;
53+ const active = groups . filter ( group => group . state === 'ACTIVE' ) . length ;
54+
55+ setStats ( {
56+ total,
57+ active,
58+ inactive : total - active
59+ } ) ;
60+ } catch ( err ) {
61+ setError ( err instanceof Error ? err . message : "Failed to fetch user groups" ) ;
62+ } finally {
63+ setLoading ( false ) ;
64+ setRefreshing ( false ) ;
65+ }
66+ } ;
67+
68+ useEffect ( ( ) => {
69+ fetchUserGroups ( ) ;
70+ } , [ environment ] ) ;
71+
72+ // Handle refresh
73+ const handleRefresh = ( ) => {
74+ setRefreshing ( true ) ;
75+ fetchUserGroups ( ) ;
76+ } ;
77+
78+ // Filter user groups based on search
79+ const filteredUserGroups = searchText
80+ ? userGroups . filter ( group =>
81+ group . name . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) ||
82+ group . id . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) )
83+ : userGroups ;
84+
85+ // Table columns
86+ const columns = [
87+ {
88+ title : 'Name' ,
89+ dataIndex : 'name' ,
90+ key : 'name' ,
91+ render : ( text : string ) => < span className = "group-name" > { text } </ span >
92+ } ,
93+ {
94+ title : 'ID' ,
95+ dataIndex : 'id' ,
96+ key : 'id' ,
97+ ellipsis : true ,
98+ } ,
99+ {
100+ title : 'Type' ,
101+ dataIndex : 'type' ,
102+ key : 'type' ,
103+ render : ( type : string ) => (
104+ < Tag color = { type === 'USER' ? 'blue' : 'purple' } >
105+ { type }
106+ </ Tag >
107+ ) ,
108+ } ,
109+ {
110+ title : 'Status' ,
111+ dataIndex : 'state' ,
112+ key : 'state' ,
113+ render : ( state : string ) => (
114+ < Tag color = { state === 'ACTIVE' ? 'green' : 'red' } >
115+ { state }
116+ </ Tag >
117+ ) ,
118+ } ,
119+ {
120+ title : 'Member Count' ,
121+ dataIndex : 'memberCount' ,
122+ key : 'memberCount' ,
123+ }
124+ ] ;
125+
126+ return (
127+ < Card >
128+ { /* Header with refresh button */ }
129+ < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "center" , marginBottom : "16px" } } >
130+ < Title level = { 5 } > User Groups in this Environment</ Title >
131+ < Button
132+ icon = { < SyncOutlined spin = { refreshing } /> }
133+ onClick = { handleRefresh }
134+ loading = { loading }
135+ >
136+ Refresh
137+ </ Button >
138+ </ div >
139+
140+ { /* Stats display */ }
141+ < div style = { { display : 'flex' , flexWrap : 'wrap' , gap : '24px' , marginBottom : '16px' } } >
142+ < div >
143+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Total Groups</ div >
144+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . total } </ div >
145+ </ div >
146+ < div >
147+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Active</ div >
148+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . active } </ div >
149+ </ div >
150+ < div >
151+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Inactive</ div >
152+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . inactive } </ div >
153+ </ div >
154+ </ div >
155+
156+ < Divider style = { { margin : "16px 0" } } />
157+
158+ { /* Error display */ }
159+ { error && (
160+ < Alert
161+ message = "Error loading user groups"
162+ description = { error }
163+ type = "error"
164+ showIcon
165+ style = { { marginBottom : "16px" } }
166+ />
167+ ) }
168+
169+ { /* Configuration warnings */ }
170+ { ( ! environment . environmentApikey || ! environment . environmentApiServiceUrl ) && ! error && (
171+ < Alert
172+ message = "Configuration Issue"
173+ description = "Missing required configuration: API key or API service URL"
174+ type = "warning"
175+ showIcon
176+ style = { { marginBottom : "16px" } }
177+ />
178+ ) }
179+
180+ { /* Content */ }
181+ { loading ? (
182+ < div style = { { display : 'flex' , justifyContent : 'center' , padding : '20px' } } >
183+ < Spin tip = "Loading user groups..." />
184+ </ div >
185+ ) : userGroups . length === 0 ? (
186+ < Empty
187+ description = { error || "No user groups found in this environment" }
188+ image = { Empty . PRESENTED_IMAGE_SIMPLE }
189+ />
190+ ) : (
191+ < >
192+ { /* Search Bar */ }
193+ < div style = { { marginBottom : 16 } } >
194+ < Search
195+ placeholder = "Search user groups by name or ID"
196+ allowClear
197+ onSearch = { value => setSearchText ( value ) }
198+ onChange = { e => setSearchText ( e . target . value ) }
199+ style = { { width : 300 } }
200+ />
201+ { searchText && filteredUserGroups . length !== userGroups . length && (
202+ < div style = { { marginTop : 8 } } >
203+ Showing { filteredUserGroups . length } of { userGroups . length } user groups
204+ </ div >
205+ ) }
206+ </ div >
207+
208+ < Table
209+ columns = { columns }
210+ dataSource = { filteredUserGroups }
211+ rowKey = "id"
212+ pagination = { { pageSize : 10 } }
213+ size = "middle"
214+ scroll = { { x : 'max-content' } }
215+ />
216+ </ >
217+ ) }
218+ </ Card >
219+ ) ;
220+ } ;
221+
222+ export default UserGroupsTab ;
0 commit comments