1- import React , { useState , useEffect , useRef } from "react" ;
2- import { Typography , Alert , Input , Button , Space , Empty , Card , Spin , Row , Col , Tooltip , Badge } from "antd" ;
3- import { SearchOutlined , CloudServerOutlined , SyncOutlined , PlusOutlined } from "@ant-design/icons" ;
1+ import React , { useState , useEffect } from "react" ;
2+ import { Alert , Empty , Spin } from "antd" ;
3+ import { SyncOutlined } from "@ant-design/icons" ;
4+ import { AddIcon , Search , TacoButton } from "lowcoder-design" ;
45import { useHistory } from "react-router-dom" ;
56import { useSelector , useDispatch } from "react-redux" ;
67import { selectEnvironments , selectEnvironmentsLoading , selectEnvironmentsError } from "redux/selectors/enterpriseSelectors" ;
@@ -9,10 +10,49 @@ import { Environment } from "./types/environment.types";
910import EnvironmentsTable from "./components/EnvironmentsTable" ;
1011import CreateEnvironmentModal from "./components/CreateEnvironmentModal" ;
1112import { buildEnvironmentId } from "@lowcoder-ee/constants/routesURL" ;
12- import { getEnvironmentTagColor } from "./utils/environmentUtils" ;
1313import { createEnvironment } from "./services/environments.service" ;
14-
15- const { Title, Text } = Typography ;
14+ import styled from "styled-components" ;
15+
16+ const EnvironmentsWrapper = styled . div `
17+ display: flex;
18+ flex-direction: column;
19+ width: 100%;
20+ height: 100%;
21+ ` ;
22+
23+ const HeaderWrapper = styled . div `
24+ display: flex;
25+ align-items: center;
26+ height: 92px;
27+ padding: 28px 36px;
28+ width: 100%;
29+ ` ;
30+
31+ const Title = styled . div `
32+ font-weight: 500;
33+ font-size: 18px;
34+ color: #222222;
35+ line-height: 18px;
36+ flex-grow: 1;
37+ ` ;
38+
39+ const AddBtn = styled ( TacoButton ) `
40+ min-width: 96px;
41+ width: fit-content;
42+ height: 32px;
43+ ` ;
44+
45+ const RefreshBtn = styled ( TacoButton ) `
46+ width: fit-content;
47+ height: 32px;
48+ margin-right: 12px;
49+ ` ;
50+
51+ const BodyWrapper = styled . div `
52+ width: 100%;
53+ flex-grow: 1;
54+ padding: 0 24px;
55+ ` ;
1656
1757/**
1858 * Environment Listing Page Component
@@ -29,13 +69,10 @@ const EnvironmentsList: React.FC = () => {
2969 const [ searchText , setSearchText ] = useState ( "" ) ;
3070 const [ isCreateModalVisible , setIsCreateModalVisible ] = useState ( false ) ;
3171 const [ isCreating , setIsCreating ] = useState ( false ) ;
32-
33-
3472
3573 // Hook for navigation
3674 const history = useHistory ( ) ;
3775
38-
3976 // Filter environments based on search text
4077 const filteredEnvironments = environments . filter ( ( env ) => {
4178 const searchLower = searchText . toLowerCase ( ) ;
@@ -82,182 +119,45 @@ const EnvironmentsList: React.FC = () => {
82119 }
83120 } ;
84121
85- // Count environment types
86- const environmentCounts = environments . reduce ( ( counts , env ) => {
87- const type = env . environmentType . toUpperCase ( ) ;
88- counts [ type ] = ( counts [ type ] || 0 ) + 1 ;
89- return counts ;
90- } , { } as Record < string , number > ) ;
91-
92122 return (
93- < div
94- className = "environments-container"
95- style = { {
96- padding : "24px" ,
97- flex : 1 ,
98- minWidth : "1000px" ,
99- // Ensure minimum width to prevent excessive shrinking
100- } }
101- >
102- { /* Modern gradient header */ }
103- < div
104- className = "environments-header"
105- style = { {
106- marginBottom : "24px" ,
107- background : 'linear-gradient(135deg, #0050b3 0%, #1890ff 100%)' ,
108- padding : '24px 32px' ,
109- borderRadius : '12px' ,
110- color : 'white' ,
111- boxShadow : '0 4px 12px rgba(0,0,0,0.1)'
112- } }
113- >
114- < Row justify = "space-between" align = "middle" gutter = { [ 16 , 16 ] } >
115- < Col xs = { 24 } sm = { 16 } >
116- < div style = { { display : 'flex' , alignItems : 'center' , gap : '20px' } } >
117- < div style = { {
118- fontSize : '36px' ,
119- backgroundColor : 'rgba(255,255,255,0.2)' ,
120- width : '72px' ,
121- height : '72px' ,
122- borderRadius : '50%' ,
123- display : 'flex' ,
124- alignItems : 'center' ,
125- justifyContent : 'center' ,
126- boxShadow : '0 4px 8px rgba(0,0,0,0.1)'
127- } } >
128- < CloudServerOutlined />
129- </ div >
130- < div >
131- < Title level = { 2 } style = { { margin : '0 0 4px 0' , color : 'white' } } >
132- Environments
133- </ Title >
134- < Text style = { { color : 'rgba(255,255,255,0.85)' , fontSize : '16px' } } >
135- Manage your deployment environments across dev, test, preprod, and production
136- </ Text >
137- </ div >
138- </ div >
139- </ Col >
140- < Col xs = { 24 } sm = { 8 } style = { { textAlign : 'right' } } >
141- < Space size = "middle" >
142- < Button
143- icon = { < PlusOutlined /> }
144- onClick = { ( ) => setIsCreateModalVisible ( true ) }
145- type = "primary"
146- style = { {
147- backgroundColor : 'rgba(255, 255, 255, 0.15)' ,
148- borderColor : 'rgba(255, 255, 255, 0.4)' ,
149- color : 'white' ,
150- fontWeight : 500
151- } }
152- >
153- Create Environment
154- </ Button >
155- < Button
156- icon = { < SyncOutlined spin = { isLoading } /> }
157- onClick = { handleRefresh }
158- loading = { isLoading }
159- type = "default"
160- style = { {
161- backgroundColor : 'rgba(255, 255, 255, 0.2)' ,
162- borderColor : 'rgba(255, 255, 255, 0.4)' ,
163- color : 'white' ,
164- fontWeight : 500
165- } }
166- >
167- Refresh
168- </ Button >
169- </ Space >
170- </ Col >
171- </ Row >
172- </ div >
173-
174- { /* Environment type stats */ }
175- { environments . length > 0 && (
176- < Row gutter = { [ 16 , 16 ] } style = { { marginBottom : '24px' } } >
177- < Col span = { 24 } >
178- < Card
179- title = "Environment Overview"
180- style = { {
181- borderRadius : '12px' ,
182- boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
183- } }
184- styles = { { header : { borderBottom : '1px solid #f0f0f0' , padding : '16px 24px' } } }
185- bodyStyle = { { padding : '24px' } }
186- >
187- < Row gutter = { [ 32 , 16 ] } justify = "space-around" >
188- < Col >
189- < Tooltip title = "Total number of environments" >
190- < div style = { { textAlign : 'center' } } >
191- < div style = { { fontSize : '38px' , fontWeight : 600 , color : '#1890ff' } } >
192- { environments . length }
193- </ div >
194- < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginTop : '4px' } } >
195- Total Environments
196- </ div >
197- </ div >
198- </ Tooltip >
199- </ Col >
200-
201- { [ 'PROD' , 'PREPROD' , 'TEST' , 'DEV' ] . map ( type => (
202- < Col key = { type } >
203- < Tooltip title = { `Number of ${ type } environments` } >
204- < div style = { { textAlign : 'center' } } >
205- < div style = { {
206- fontSize : '38px' ,
207- fontWeight : 600 ,
208- color : getEnvironmentTagColor ( type ) === 'default' ? '#8c8c8c' : getEnvironmentTagColor ( type )
209- } } >
210- { environmentCounts [ type ] || 0 }
211- </ div >
212- < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginTop : '4px' , display : 'flex' , justifyContent : 'center' , alignItems : 'center' , gap : '6px' } } >
213- < Badge color = { getEnvironmentTagColor ( type ) } />
214- { type } Environments
215- </ div >
216- </ div >
217- </ Tooltip >
218- </ Col >
219- ) ) }
220- </ Row >
221- </ Card >
222- </ Col >
223- </ Row >
224- ) }
225-
226- { /* Main content card */ }
227- < Card
228- title = "Environment List"
229- style = { {
230- borderRadius : '12px' ,
231- boxShadow : '0 2px 8px rgba(0,0,0,0.05)' ,
232- } }
233- styles = { { header : { borderBottom : '1px solid #f0f0f0' , padding : '16px 24px' } } }
234- bodyStyle = { { padding : '24px' } }
235- extra = {
236- < Input
237- placeholder = "Search environments"
238- value = { searchText }
239- onChange = { ( e ) => setSearchText ( e . target . value ) }
240- style = { { width : 250 } }
241- prefix = { < SearchOutlined /> }
242- allowClear
243- />
244- }
245- >
123+ < EnvironmentsWrapper >
124+ < HeaderWrapper >
125+ < Title > Environments</ Title >
126+ < Search
127+ placeholder = "Search"
128+ value = { searchText }
129+ onChange = { ( e ) => setSearchText ( e . target . value ) }
130+ style = { { width : "192px" , height : "32px" , margin : "0 12px 0 0" } }
131+ />
132+ < RefreshBtn
133+ buttonType = "normal"
134+ icon = { < SyncOutlined spin = { isLoading } /> }
135+ onClick = { handleRefresh }
136+ loading = { isLoading }
137+ >
138+ Refresh
139+ </ RefreshBtn >
140+ < AddBtn buttonType = "primary" onClick = { ( ) => setIsCreateModalVisible ( true ) } >
141+ New Environment
142+ </ AddBtn >
143+ </ HeaderWrapper >
144+
145+ < BodyWrapper >
246146 { /* Error handling */ }
247147 { error && (
248148 < Alert
249149 message = "Error loading environments"
250150 description = { error }
251151 type = "error"
252152 showIcon
253- style = { { marginBottom : "24px " } }
153+ style = { { marginBottom : "16px " } }
254154 />
255155 ) }
256156
257157 { /* Loading, empty state or table */ }
258158 { isLoading ? (
259159 < div style = { { display : 'flex' , justifyContent : 'center' , padding : '60px 0' } } >
260- < Spin size = "large" tip = "Loading environments..." />
160+ < Spin size = "large" />
261161 </ div >
262162 ) : environments . length === 0 && ! error ? (
263163 < Empty
@@ -286,7 +186,7 @@ const EnvironmentsList: React.FC = () => {
286186 Showing { filteredEnvironments . length } of { environments . length } environments
287187 </ div >
288188 ) }
289- </ Card >
189+ </ BodyWrapper >
290190
291191 { /* Create Environment Modal */ }
292192 < CreateEnvironmentModal
@@ -295,7 +195,7 @@ const EnvironmentsList: React.FC = () => {
295195 onSave = { handleCreateEnvironment }
296196 loading = { isCreating }
297197 />
298- </ div >
198+ </ EnvironmentsWrapper >
299199 ) ;
300200} ;
301201
0 commit comments