1
1
import React , { useState , useEffect } from "react" ;
2
- import { Alert , Empty , Spin , Card } from "antd" ;
2
+ import { Alert , Empty , Spin , Card , Row , Col } from "antd" ;
3
3
import { SyncOutlined , CloudServerOutlined } from "@ant-design/icons" ;
4
4
import { AddIcon , Search , TacoButton } from "lowcoder-design" ;
5
5
import { useHistory } from "react-router-dom" ;
@@ -9,6 +9,7 @@ import { fetchEnvironments } from "redux/reduxActions/enterpriseActions";
9
9
import { Environment } from "./types/environment.types" ;
10
10
import EnvironmentsTable from "./components/EnvironmentsTable" ;
11
11
import CreateEnvironmentModal from "./components/CreateEnvironmentModal" ;
12
+ import StatsCard from "./components/StatsCard" ;
12
13
import { buildEnvironmentId } from "@lowcoder-ee/constants/routesURL" ;
13
14
import { createEnvironment } from "./services/environments.service" ;
14
15
import { getEnvironmentTagColor } from "./utils/environmentUtils" ;
@@ -107,37 +108,6 @@ const EnvironmentsList: React.FC = () => {
107
108
return < CloudServerOutlined /> ;
108
109
} ;
109
110
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
-
141
111
// Filter environments based on search text
142
112
const filteredEnvironments = environments . filter ( ( env ) => {
143
113
const searchLower = searchText . toLowerCase ( ) ;
@@ -201,75 +171,65 @@ const EnvironmentsList: React.FC = () => {
201
171
>
202
172
Refresh
203
173
</ 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
206
180
</ AddBtn >
207
181
</ HeaderWrapper >
208
182
209
183
< 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 >
226
199
227
- { /* Error handling */ }
228
200
{ error && (
229
201
< Alert
230
202
message = "Error loading environments"
231
203
description = { error }
232
204
type = "error"
233
205
showIcon
234
- style = { { marginBottom : "16px" } }
206
+ style = { { marginBottom : 16 } }
235
207
/>
236
208
) }
237
209
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" } }
248
214
/>
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" } }
254
221
/>
255
- ) : (
256
- /* Table component */
222
+ ) }
223
+
224
+ { ( filteredEnvironments . length > 0 || isLoading ) && (
257
225
< EnvironmentsTable
258
226
environments = { filteredEnvironments }
259
227
loading = { isLoading }
260
228
onRowClick = { handleRowClick }
261
229
/>
262
230
) }
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
- ) }
270
231
</ BodyWrapper >
271
232
272
- { /* Create Environment Modal */ }
273
233
< CreateEnvironmentModal
274
234
visible = { isCreateModalVisible }
275
235
onClose = { ( ) => setIsCreateModalVisible ( false ) }
0 commit comments