Skip to content

Commit 3e35b5d

Browse files
committed
update UI for workspaces tab
1 parent c3a770e commit 3e35b5d

File tree

1 file changed

+176
-82
lines changed

1 file changed

+176
-82
lines changed

client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx

Lines changed: 176 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState, useEffect } from 'react';
2-
import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd';
3-
import { SyncOutlined, CloudUploadOutlined, AuditOutlined } from '@ant-design/icons';
2+
import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col, Avatar } from 'antd';
3+
import { SyncOutlined, AuditOutlined, TeamOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined } from '@ant-design/icons';
44
import Title from 'antd/lib/typography/Title';
55
import { Environment } from '../types/environment.types';
66
import { Workspace } from '../types/workspace.types';
@@ -68,9 +68,6 @@ const WorkspacesTab: React.FC<WorkspacesTabProps> = ({ environment }) => {
6868
fetchWorkspaces();
6969
};
7070

71-
// Toggle managed status
72-
73-
7471
// Handle row click for navigation
7572
const handleRowClick = (workspace: Workspace) => {
7673
history.push(`/setting/environments/${environment.environmentId}/workspaces/${workspace.id}`);
@@ -83,19 +80,72 @@ const WorkspacesTab: React.FC<WorkspacesTabProps> = ({ environment }) => {
8380
workspace.id.toLowerCase().includes(searchText.toLowerCase()))
8481
: workspaces;
8582

83+
// Helper function to generate colors from strings
84+
const stringToColor = (str: string) => {
85+
let hash = 0;
86+
for (let i = 0; i < str.length; i++) {
87+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
88+
}
89+
90+
const hue = Math.abs(hash % 360);
91+
return `hsl(${hue}, 70%, 50%)`;
92+
};
93+
94+
// Stat card component
95+
const StatCard = ({ title, value, icon }: { title: string; value: number; icon: React.ReactNode }) => (
96+
<Card
97+
style={{
98+
height: '100%',
99+
borderRadius: '8px',
100+
boxShadow: '0 2px 8px rgba(0,0,0,0.05)'
101+
}}
102+
>
103+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
104+
<div>
105+
<div style={{ fontSize: '14px', color: '#8c8c8c', marginBottom: '8px' }}>{title}</div>
106+
<div style={{ fontSize: '24px', fontWeight: 600 }}>{value}</div>
107+
</div>
108+
<div style={{
109+
fontSize: '28px',
110+
opacity: 0.8,
111+
color: '#52c41a',
112+
padding: '12px',
113+
backgroundColor: 'rgba(82, 196, 26, 0.1)',
114+
borderRadius: '50%',
115+
display: 'flex',
116+
alignItems: 'center',
117+
justifyContent: 'center'
118+
}}>
119+
{icon}
120+
</div>
121+
</div>
122+
</Card>
123+
);
124+
86125
// Table columns
87126
const columns = [
88127
{
89-
title: 'Name',
90-
dataIndex: 'name',
91-
key: 'name',
92-
render: (text: string) => <span className="workspace-name">{text}</span>
93-
},
94-
{
95-
title: 'ID',
96-
dataIndex: 'id',
97-
key: 'id',
98-
ellipsis: true,
128+
title: 'Workspace',
129+
key: 'workspace',
130+
render: (workspace: Workspace) => (
131+
<div style={{ display: 'flex', alignItems: 'center' }}>
132+
<Avatar
133+
style={{
134+
backgroundColor: stringToColor(workspace.name),
135+
marginRight: 12
136+
}}
137+
shape="square"
138+
>
139+
{workspace.name.charAt(0).toUpperCase()}
140+
</Avatar>
141+
<div>
142+
<div style={{ fontWeight: 500 }}>{workspace.name}</div>
143+
<div style={{ fontSize: 12, color: '#8c8c8c', marginTop: 4 }}>
144+
{workspace.id}
145+
</div>
146+
</div>
147+
</div>
148+
),
99149
},
100150
{
101151
title: 'Role',
@@ -107,7 +157,8 @@ const WorkspacesTab: React.FC<WorkspacesTabProps> = ({ environment }) => {
107157
dataIndex: 'status',
108158
key: 'status',
109159
render: (status: string) => (
110-
<Tag color={status === 'ACTIVE' ? 'green' : 'red'}>
160+
<Tag color={status === 'ACTIVE' ? 'green' : 'red'} style={{ borderRadius: '12px' }}>
161+
{status === 'ACTIVE' ? <CheckCircleFilled style={{ marginRight: 4 }} /> : null}
111162
{status}
112163
</Tag>
113164
),
@@ -116,7 +167,14 @@ const WorkspacesTab: React.FC<WorkspacesTabProps> = ({ environment }) => {
116167
title: 'Managed',
117168
key: 'managed',
118169
render: (_: any, workspace: Workspace) => (
119-
<Tag color={workspace.managed ? 'blue' : 'default'}>
170+
<Tag
171+
color={workspace.managed ? 'processing' : 'default'}
172+
style={{ borderRadius: '12px' }}
173+
>
174+
{workspace.managed
175+
? <CloudServerOutlined style={{ marginRight: 4 }} />
176+
: <DisconnectOutlined style={{ marginRight: 4 }} />
177+
}
120178
{workspace.managed ? 'Managed' : 'Unmanaged'}
121179
</Tag>
122180
),
@@ -129,7 +187,6 @@ const WorkspacesTab: React.FC<WorkspacesTabProps> = ({ environment }) => {
129187
<Tooltip title="View Audit Logs">
130188
<Button
131189
icon={<AuditOutlined />}
132-
size="small"
133190
onClick={(e) => {
134191
e.stopPropagation();
135192
const auditUrl = `/setting/audit?environmentId=${environment.environmentId}&orgId=${workspace.id}&pageSize=100&pageNum=1`;
@@ -145,45 +202,43 @@ const WorkspacesTab: React.FC<WorkspacesTabProps> = ({ environment }) => {
145202
];
146203

147204
return (
148-
<Card>
149-
{/* Header with refresh button */}
150-
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }}>
151-
<Title level={5}>Workspaces in this Environment</Title>
205+
<div style={{ padding: '16px 0' }}>
206+
{/* Header */}
207+
<div style={{
208+
display: "flex",
209+
justifyContent: "space-between",
210+
alignItems: "center",
211+
marginBottom: "24px",
212+
background: 'linear-gradient(135deg, #52c41a 0%, #13c2c2 100%)',
213+
padding: '20px 24px',
214+
borderRadius: '8px',
215+
color: 'white'
216+
}}>
217+
<div>
218+
<Title level={4} style={{ color: 'white', margin: 0 }}>
219+
<TeamOutlined style={{ marginRight: 10 }} /> Workspaces
220+
</Title>
221+
<p style={{ marginBottom: 0 }}>Manage workspaces in this environment</p>
222+
</div>
152223
<Button
153224
icon={<SyncOutlined spin={refreshing} />}
154225
onClick={handleRefresh}
155226
loading={loading}
227+
type="primary"
228+
ghost
156229
>
157230
Refresh
158231
</Button>
159232
</div>
160233

161-
{/* Stats display */}
162-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '24px', marginBottom: '16px' }}>
163-
<div>
164-
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Total Workspaces</div>
165-
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.total}</div>
166-
</div>
167-
<div>
168-
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Managed</div>
169-
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.managed}</div>
170-
</div>
171-
<div>
172-
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Unmanaged</div>
173-
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.unmanaged}</div>
174-
</div>
175-
</div>
176-
177-
<Divider style={{ margin: "16px 0" }} />
178-
179234
{/* Error display */}
180235
{error && (
181236
<Alert
182237
message="Error loading workspaces"
183238
description={error}
184239
type="error"
185240
showIcon
186-
style={{ marginBottom: "16px" }}
241+
style={{ marginBottom: "20px" }}
187242
/>
188243
)}
189244

@@ -194,53 +249,92 @@ const WorkspacesTab: React.FC<WorkspacesTabProps> = ({ environment }) => {
194249
description="Missing required configuration: API key or API service URL"
195250
type="warning"
196251
showIcon
197-
style={{ marginBottom: "16px" }}
252+
style={{ marginBottom: "20px" }}
198253
/>
199254
)}
200255

256+
{/* Stats display */}
257+
<Row gutter={[16, 16]} style={{ marginBottom: '24px' }}>
258+
<Col xs={24} sm={8}>
259+
<StatCard
260+
title="Total Workspaces"
261+
value={stats.total}
262+
icon={<TeamOutlined />}
263+
/>
264+
</Col>
265+
<Col xs={24} sm={8}>
266+
<StatCard
267+
title="Managed Workspaces"
268+
value={stats.managed}
269+
icon={<CloudServerOutlined />}
270+
/>
271+
</Col>
272+
<Col xs={24} sm={8}>
273+
<StatCard
274+
title="Unmanaged Workspaces"
275+
value={stats.unmanaged}
276+
icon={<DisconnectOutlined />}
277+
/>
278+
</Col>
279+
</Row>
280+
201281
{/* Content */}
202-
{loading ? (
203-
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
204-
<Spin tip="Loading workspaces..." />
205-
</div>
206-
) : workspaces.length === 0 ? (
207-
<Empty
208-
description={error || "No workspaces found in this environment"}
209-
image={Empty.PRESENTED_IMAGE_SIMPLE}
210-
/>
211-
) : (
212-
<>
213-
{/* Search Bar */}
214-
<div style={{ marginBottom: 16 }}>
215-
<Search
216-
placeholder="Search workspaces by name or ID"
217-
allowClear
218-
onSearch={value => setSearchText(value)}
219-
onChange={e => setSearchText(e.target.value)}
220-
style={{ width: 300 }}
221-
/>
222-
{searchText && filteredWorkspaces.length !== workspaces.length && (
223-
<div style={{ marginTop: 8 }}>
224-
Showing {filteredWorkspaces.length} of {workspaces.length} workspaces
225-
</div>
226-
)}
282+
<Card
283+
style={{
284+
borderRadius: '8px',
285+
boxShadow: '0 2px 8px rgba(0,0,0,0.05)'
286+
}}
287+
>
288+
{loading ? (
289+
<div style={{ display: 'flex', justifyContent: 'center', padding: '40px' }}>
290+
<Spin size="large" tip="Loading workspaces..." />
227291
</div>
228-
229-
<Table
230-
columns={columns}
231-
dataSource={filteredWorkspaces}
232-
rowKey="id"
233-
pagination={{ pageSize: 10 }}
234-
size="middle"
235-
scroll={{ x: 'max-content' }}
236-
onRow={(record) => ({
237-
onClick: () => handleRowClick(record),
238-
style: { cursor: 'pointer' }
239-
})}
292+
) : workspaces.length === 0 ? (
293+
<Empty
294+
description={error || "No workspaces found in this environment"}
295+
image={Empty.PRESENTED_IMAGE_SIMPLE}
240296
/>
241-
</>
242-
)}
243-
</Card>
297+
) : (
298+
<>
299+
{/* Search Bar */}
300+
<div style={{ marginBottom: 20 }}>
301+
<Search
302+
placeholder="Search workspaces by name or ID"
303+
allowClear
304+
onSearch={value => setSearchText(value)}
305+
onChange={e => setSearchText(e.target.value)}
306+
style={{ width: 300 }}
307+
size="large"
308+
/>
309+
{searchText && filteredWorkspaces.length !== workspaces.length && (
310+
<div style={{ marginTop: 8, color: '#8c8c8c' }}>
311+
Showing {filteredWorkspaces.length} of {workspaces.length} workspaces
312+
</div>
313+
)}
314+
</div>
315+
316+
<Table
317+
columns={columns}
318+
dataSource={filteredWorkspaces}
319+
rowKey="id"
320+
pagination={{
321+
pageSize: 10,
322+
showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} workspaces`
323+
}}
324+
style={{
325+
borderRadius: '8px',
326+
overflow: 'hidden'
327+
}}
328+
onRow={(record) => ({
329+
onClick: () => handleRowClick(record),
330+
style: { cursor: 'pointer' }
331+
})}
332+
rowClassName={() => 'workspace-row'}
333+
/>
334+
</>
335+
)}
336+
</Card>
337+
</div>
244338
);
245339
};
246340

0 commit comments

Comments
 (0)