11import React , { useState , useEffect } from "react" ;
2- import { Alert , Empty , Spin , Card } from "antd" ;
2+ import { Alert , Empty , Spin , Card , Row , Col } from "antd" ;
33import { SyncOutlined , CloudServerOutlined } from "@ant-design/icons" ;
44import { AddIcon , Search , TacoButton } from "lowcoder-design" ;
55import { useHistory } from "react-router-dom" ;
@@ -9,6 +9,7 @@ import { fetchEnvironments } from "redux/reduxActions/enterpriseActions";
99import { Environment } from "./types/environment.types" ;
1010import EnvironmentsTable from "./components/EnvironmentsTable" ;
1111import CreateEnvironmentModal from "./components/CreateEnvironmentModal" ;
12+ import StatsCard from "./components/StatsCard" ;
1213import { buildEnvironmentId } from "@lowcoder-ee/constants/routesURL" ;
1314import { createEnvironment } from "./services/environments.service" ;
1415import { getEnvironmentTagColor } from "./utils/environmentUtils" ;
@@ -107,37 +108,6 @@ const EnvironmentsList: React.FC = () => {
107108 return < CloudServerOutlined /> ;
108109 } ;
109110
110- // Stat card component
111- const StatCard = ( { title, value, color } : { title : string ; value : number ; color : string } ) => (
112- < Card
113- style = { {
114- height : '100%' ,
115- borderRadius : '4px' ,
116- border : '1px solid #f0f0f0'
117- } }
118- >
119- < div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' } } >
120- < div >
121- < div style = { { fontSize : '13px' , color : '#8c8c8c' , marginBottom : '8px' } } > { title } </ div >
122- < div style = { { fontSize : '20px' , fontWeight : 500 } } > { value } </ div >
123- </ div >
124- < div style = { {
125- fontSize : '24px' ,
126- opacity : 0.8 ,
127- color : color ,
128- padding : '8px' ,
129- backgroundColor : `${ color } 15` ,
130- borderRadius : '4px' ,
131- display : 'flex' ,
132- alignItems : 'center' ,
133- justifyContent : 'center'
134- } } >
135- { getEnvironmentIcon ( title ) }
136- </ div >
137- </ div >
138- </ Card >
139- ) ;
140-
141111 // Filter environments based on search text
142112 const filteredEnvironments = environments . filter ( ( env ) => {
143113 const searchLower = searchText . toLowerCase ( ) ;
@@ -201,75 +171,65 @@ const EnvironmentsList: React.FC = () => {
201171 >
202172 Refresh
203173 </ RefreshBtn >
204- < AddBtn buttonType = "primary" onClick = { ( ) => setIsCreateModalVisible ( true ) } >
205- New Environment
174+ < AddBtn
175+ buttonType = "primary"
176+ icon = { < AddIcon /> }
177+ onClick = { ( ) => setIsCreateModalVisible ( true ) }
178+ >
179+ Add Environment
206180 </ AddBtn >
207181 </ HeaderWrapper >
208182
209183 < BodyWrapper >
210- { /* Environment Type Statistics */ }
211- { ! isLoading && environments . length > 0 && (
212- < StatsWrapper >
213- < div style = { { display : 'flex' , gap : '16px' , marginBottom : '20px' , flexWrap : 'wrap' } } >
214- { environmentStats . map ( ( [ type , count ] ) => (
215- < div key = { type } style = { { minWidth : '200px' , flex : '1' } } >
216- < StatCard
217- title = { type }
218- value = { count }
219- color = { getEnvironmentTagColor ( type . toLowerCase ( ) ) }
220- />
221- </ div >
222- ) ) }
223- </ div >
224- </ StatsWrapper >
225- ) }
184+ { /* Environment Statistics Cards */ }
185+ < StatsWrapper >
186+ < Row gutter = { [ 16 , 16 ] } >
187+ { environmentStats . map ( ( [ type , count ] ) => (
188+ < Col xs = { 24 } sm = { 12 } md = { 8 } lg = { 6 } key = { type } >
189+ < StatsCard
190+ title = { `${ type } Environments` }
191+ value = { count }
192+ icon = { getEnvironmentIcon ( type ) }
193+ color = { getEnvironmentTagColor ( type ) }
194+ />
195+ </ Col >
196+ ) ) }
197+ </ Row >
198+ </ StatsWrapper >
226199
227- { /* Error handling */ }
228200 { error && (
229201 < Alert
230202 message = "Error loading environments"
231203 description = { error }
232204 type = "error"
233205 showIcon
234- style = { { marginBottom : "16px" } }
206+ style = { { marginBottom : 16 } }
235207 />
236208 ) }
237209
238- { /* Loading, empty state or table */ }
239- { isLoading ? (
240- < div style = { { display : 'flex' , justifyContent : 'center' , padding : '60px 0' } } >
241- < Spin size = "large" />
242- </ div >
243- ) : environments . length === 0 && ! error ? (
244- < Empty
245- description = "No environments found"
246- image = { Empty . PRESENTED_IMAGE_SIMPLE }
247- style = { { padding : '60px 0' } }
210+ { ! isLoading && ! error && filteredEnvironments . length === 0 && searchText && (
211+ < Empty
212+ description = { `No environments found matching "${ searchText } "` }
213+ style = { { margin : "60px 0" } }
248214 />
249- ) : filteredEnvironments . length === 0 ? (
250- < Empty
251- description = "No environments match your search"
252- image = { Empty . PRESENTED_IMAGE_SIMPLE }
253- style = { { padding : '60px 0' } }
215+ ) }
216+
217+ { ! isLoading && ! error && environments . length === 0 && ! searchText && (
218+ < Empty
219+ description = "No environments found. Create your first environment to get started."
220+ style = { { margin : "60px 0" } }
254221 />
255- ) : (
256- /* Table component */
222+ ) }
223+
224+ { ( filteredEnvironments . length > 0 || isLoading ) && (
257225 < EnvironmentsTable
258226 environments = { filteredEnvironments }
259227 loading = { isLoading }
260228 onRowClick = { handleRowClick }
261229 />
262230 ) }
263-
264- { /* Results counter when searching */ }
265- { searchText && filteredEnvironments . length !== environments . length && (
266- < div style = { { marginTop : 16 , color : '#8c8c8c' , textAlign : 'right' } } >
267- Showing { filteredEnvironments . length } of { environments . length } environments
268- </ div >
269- ) }
270231 </ BodyWrapper >
271232
272- { /* Create Environment Modal */ }
273233 < CreateEnvironmentModal
274234 visible = { isCreateModalVisible }
275235 onClose = { ( ) => setIsCreateModalVisible ( false ) }
0 commit comments