1
1
import React , { useState } from "react" ;
2
- import { Typography , Alert , Input , Button , Space , Empty } from "antd" ;
3
- import { SearchOutlined , ReloadOutlined } from "@ant-design/icons" ;
2
+ import { Typography , Alert , Input , Button , Space , Empty , Card , Spin , Row , Col , Tooltip , Badge } from "antd" ;
3
+ import { SearchOutlined , ReloadOutlined , PlusOutlined , EnvironmentOutlined } from "@ant-design/icons" ;
4
4
import { useHistory } from "react-router-dom" ;
5
5
import { useEnvironmentContext } from "./context/EnvironmentContext" ;
6
6
import { Environment } from "./types/environment.types" ;
7
7
import EnvironmentsTable from "./components/EnvironmentsTable" ;
8
8
import { buildEnvironmentId } from "@lowcoder-ee/constants/routesURL" ;
9
- import EditEnvironmentModal from "./components/EditEnvironmentModal " ;
9
+ import { getEnvironmentTagColor } from "./utils/environmentUtils " ;
10
10
11
- const { Title } = Typography ;
11
+ const { Title, Text } = Typography ;
12
12
13
13
/**
14
14
* Environment Listing Page Component
@@ -19,13 +19,13 @@ const EnvironmentsList: React.FC = () => {
19
19
const {
20
20
environments,
21
21
isLoading,
22
- error,
22
+ error,
23
+ refreshEnvironments
23
24
} = useEnvironmentContext ( ) ;
24
25
25
- console . log ( "Environments:" , environments ) ;
26
-
27
26
// State for search input
28
27
const [ searchText , setSearchText ] = useState ( "" ) ;
28
+ const [ isRefreshing , setIsRefreshing ] = useState ( false ) ;
29
29
30
30
// Hook for navigation
31
31
const history = useHistory ( ) ;
@@ -46,20 +46,160 @@ const EnvironmentsList: React.FC = () => {
46
46
history . push ( buildEnvironmentId ( record . environmentId ) ) ;
47
47
} ;
48
48
49
+ // Handle refresh
50
+ const handleRefresh = async ( ) => {
51
+ setIsRefreshing ( true ) ;
52
+ await refreshEnvironments ( ) ;
53
+ setIsRefreshing ( false ) ;
54
+ } ;
55
+
56
+ // Count environment types
57
+ const environmentCounts = environments . reduce ( ( counts , env ) => {
58
+ const type = env . environmentType . toUpperCase ( ) ;
59
+ counts [ type ] = ( counts [ type ] || 0 ) + 1 ;
60
+ return counts ;
61
+ } , { } as Record < string , number > ) ;
62
+
49
63
return (
50
- < div className = "environments-container" style = { { padding : "24px" , flex :1 } } >
51
- { /* Header section with title and controls */ }
64
+ < div
65
+ className = "environments-container"
66
+ style = { {
67
+ padding : "24px" ,
68
+ flex : 1 ,
69
+ minWidth : "1000px" ,
70
+ // Ensure minimum width to prevent excessive shrinking
71
+ } }
72
+ >
73
+ { /* Modern gradient header */ }
52
74
< div
53
75
className = "environments-header"
54
76
style = { {
55
77
marginBottom : "24px" ,
56
- display : "flex" ,
57
- justifyContent : "space-between" ,
58
- alignItems : "center" ,
78
+ background : 'linear-gradient(135deg, #0050b3 0%, #1890ff 100%)' ,
79
+ padding : '24px 32px' ,
80
+ borderRadius : '12px' ,
81
+ color : 'white' ,
82
+ boxShadow : '0 4px 12px rgba(0,0,0,0.1)'
59
83
} }
60
84
>
61
- < Title level = { 3 } > Environments</ Title >
62
- < Space >
85
+ < Row justify = "space-between" align = "middle" gutter = { [ 16 , 16 ] } >
86
+ < Col xs = { 24 } sm = { 16 } >
87
+ < div style = { { display : 'flex' , alignItems : 'center' , gap : '20px' } } >
88
+ < div style = { {
89
+ fontSize : '36px' ,
90
+ backgroundColor : 'rgba(255,255,255,0.2)' ,
91
+ width : '72px' ,
92
+ height : '72px' ,
93
+ borderRadius : '50%' ,
94
+ display : 'flex' ,
95
+ alignItems : 'center' ,
96
+ justifyContent : 'center' ,
97
+ boxShadow : '0 4px 8px rgba(0,0,0,0.1)'
98
+ } } >
99
+ < EnvironmentOutlined />
100
+ </ div >
101
+ < div >
102
+ < Title level = { 2 } style = { { margin : '0 0 4px 0' , color : 'white' } } >
103
+ Environments
104
+ </ Title >
105
+ < Text style = { { color : 'rgba(255,255,255,0.85)' , fontSize : '16px' } } >
106
+ Manage your deployment environments across dev, test, preprod, and production
107
+ </ Text >
108
+ </ div >
109
+ </ div >
110
+ </ Col >
111
+ < Col xs = { 24 } sm = { 8 } style = { { textAlign : 'right' } } >
112
+ < Space size = "middle" >
113
+ < Button
114
+ icon = { < ReloadOutlined spin = { isRefreshing } /> }
115
+ onClick = { handleRefresh }
116
+ type = "primary"
117
+ ghost
118
+ loading = { isLoading && ! isRefreshing }
119
+ size = "large"
120
+ >
121
+ Refresh
122
+ </ Button >
123
+ < Button
124
+ icon = { < PlusOutlined /> }
125
+ type = "primary"
126
+ size = "large"
127
+ >
128
+ New Environment
129
+ </ Button >
130
+ </ Space >
131
+ </ Col >
132
+ </ Row >
133
+ </ div >
134
+
135
+ { /* Environment type stats */ }
136
+ { environments . length > 0 && (
137
+ < Row gutter = { [ 16 , 16 ] } style = { { marginBottom : '24px' } } >
138
+ < Col span = { 24 } >
139
+ < Card
140
+ title = "Environment Overview"
141
+ style = { {
142
+ borderRadius : '12px' ,
143
+ boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
144
+ } }
145
+ headStyle = { {
146
+ borderBottom : '1px solid #f0f0f0' ,
147
+ padding : '16px 24px'
148
+ } }
149
+ bodyStyle = { { padding : '24px' } }
150
+ >
151
+ < Row gutter = { [ 32 , 16 ] } justify = "space-around" >
152
+ < Col >
153
+ < Tooltip title = "Total number of environments" >
154
+ < div style = { { textAlign : 'center' } } >
155
+ < div style = { { fontSize : '38px' , fontWeight : 600 , color : '#1890ff' } } >
156
+ { environments . length }
157
+ </ div >
158
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginTop : '4px' } } >
159
+ Total Environments
160
+ </ div >
161
+ </ div >
162
+ </ Tooltip >
163
+ </ Col >
164
+
165
+ { [ 'PROD' , 'PREPROD' , 'TEST' , 'DEV' ] . map ( type => (
166
+ < Col key = { type } >
167
+ < Tooltip title = { `Number of ${ type } environments` } >
168
+ < div style = { { textAlign : 'center' } } >
169
+ < div style = { {
170
+ fontSize : '38px' ,
171
+ fontWeight : 600 ,
172
+ color : getEnvironmentTagColor ( type ) === 'default' ? '#8c8c8c' : getEnvironmentTagColor ( type )
173
+ } } >
174
+ { environmentCounts [ type ] || 0 }
175
+ </ div >
176
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginTop : '4px' , display : 'flex' , justifyContent : 'center' , alignItems : 'center' , gap : '6px' } } >
177
+ < Badge color = { getEnvironmentTagColor ( type ) } />
178
+ { type } Environments
179
+ </ div >
180
+ </ div >
181
+ </ Tooltip >
182
+ </ Col >
183
+ ) ) }
184
+ </ Row >
185
+ </ Card >
186
+ </ Col >
187
+ </ Row >
188
+ ) }
189
+
190
+ { /* Main content card */ }
191
+ < Card
192
+ title = "Environment List"
193
+ style = { {
194
+ borderRadius : '12px' ,
195
+ boxShadow : '0 2px 8px rgba(0,0,0,0.05)' ,
196
+ } }
197
+ headStyle = { {
198
+ borderBottom : '1px solid #f0f0f0' ,
199
+ padding : '16px 24px'
200
+ } }
201
+ bodyStyle = { { padding : '24px' } }
202
+ extra = {
63
203
< Input
64
204
placeholder = "Search environments"
65
205
value = { searchText }
@@ -68,34 +208,52 @@ const EnvironmentsList: React.FC = () => {
68
208
prefix = { < SearchOutlined /> }
69
209
allowClear
70
210
/>
71
- </ Space >
72
- </ div >
211
+ }
212
+ >
213
+ { /* Error handling */ }
214
+ { error && (
215
+ < Alert
216
+ message = "Error loading environments"
217
+ description = { error }
218
+ type = "error"
219
+ showIcon
220
+ style = { { marginBottom : "24px" } }
221
+ />
222
+ ) }
73
223
74
- { /* Error handling */ }
75
- { error && (
76
- < Alert
77
- message = "Error loading environments"
78
- description = { error }
79
- type = "error"
80
- showIcon
81
- style = { { marginBottom : "24px" } }
82
- />
83
- ) }
224
+ { /* Loading, empty state or table */ }
225
+ { isLoading ? (
226
+ < div style = { { display : 'flex' , justifyContent : 'center' , padding : '60px 0' } } >
227
+ < Spin size = "large" tip = "Loading environments..." />
228
+ </ div >
229
+ ) : environments . length === 0 && ! error ? (
230
+ < Empty
231
+ description = "No environments found"
232
+ image = { Empty . PRESENTED_IMAGE_SIMPLE }
233
+ style = { { padding : '60px 0' } }
234
+ />
235
+ ) : filteredEnvironments . length === 0 ? (
236
+ < Empty
237
+ description = "No environments match your search"
238
+ image = { Empty . PRESENTED_IMAGE_SIMPLE }
239
+ style = { { padding : '60px 0' } }
240
+ />
241
+ ) : (
242
+ /* Table component */
243
+ < EnvironmentsTable
244
+ environments = { filteredEnvironments }
245
+ loading = { isLoading }
246
+ onRowClick = { handleRowClick }
247
+ />
248
+ ) }
84
249
85
- { /* Empty state handling */ }
86
- { ! isLoading && environments . length === 0 && ! error ? (
87
- < Empty
88
- description = "No environments found"
89
- image = { Empty . PRESENTED_IMAGE_SIMPLE }
90
- />
91
- ) : (
92
- /* Table component */
93
- < EnvironmentsTable
94
- environments = { filteredEnvironments }
95
- loading = { isLoading }
96
- onRowClick = { handleRowClick }
97
- />
98
- ) }
250
+ { /* Results counter when searching */ }
251
+ { searchText && filteredEnvironments . length !== environments . length && (
252
+ < div style = { { marginTop : 16 , color : '#8c8c8c' , textAlign : 'right' } } >
253
+ Showing { filteredEnvironments . length } of { environments . length } environments
254
+ </ div >
255
+ ) }
256
+ </ Card >
99
257
</ div >
100
258
) ;
101
259
} ;
0 commit comments