Skip to content

Commit f4fba8e

Browse files
committed
Update Apps UI
1 parent 199c869 commit f4fba8e

File tree

1 file changed

+184
-87
lines changed
  • client/packages/lowcoder/src/pages/setting/environments/components

1 file changed

+184
-87
lines changed

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

Lines changed: 184 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
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 } from 'antd';
3+
import { SyncOutlined, CloudUploadOutlined, AuditOutlined, AppstoreOutlined, 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';
77
import { App, AppStats } from '../types/app.types';
88
import { getMergedWorkspaceApps } from '../services/apps.service';
9-
import { Switch, Spin, Empty } from 'antd';
9+
import { Switch, Spin, Empty, Avatar } from 'antd';
1010
import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service';
1111
import { useDeployModal } from '../context/DeployModalContext';
1212
import { appsConfig } from '../config/apps.config';
@@ -136,25 +136,43 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
136136
// Table columns
137137
const columns = [
138138
{
139-
title: 'Name',
140-
dataIndex: 'name',
141-
key: 'name',
142-
render: (text: string) => <span className="app-name">{text}</span>
143-
},
144-
{
145-
title: 'ID',
146-
dataIndex: 'applicationId',
147-
key: 'applicationId',
148-
ellipsis: true,
139+
title: 'App',
140+
key: 'app',
141+
render: (app: App) => (
142+
<div style={{ display: 'flex', alignItems: 'center' }}>
143+
<Avatar
144+
style={{
145+
backgroundColor: stringToColor(app.name),
146+
marginRight: 12
147+
}}
148+
shape="square"
149+
>
150+
{app.name.charAt(0).toUpperCase()}
151+
</Avatar>
152+
<div>
153+
<div style={{ fontWeight: 500 }}>{app.name}</div>
154+
<div style={{ fontSize: 12, color: '#8c8c8c', marginTop: 4 }}>
155+
{app.applicationId}
156+
</div>
157+
</div>
158+
</div>
159+
),
149160
},
150161
{
151-
title: 'Published',
152-
dataIndex: 'published',
153-
key: 'published',
154-
render: (published: boolean) => (
155-
<Tag color={published ? 'green' : 'default'}>
156-
{published ? 'Published' : 'Draft'}
157-
</Tag>
162+
title: 'Status',
163+
key: 'status',
164+
render: (app: App) => (
165+
<Space direction="vertical" size={0}>
166+
<Tag color={app.published ? 'success' : 'default'} style={{ borderRadius: '12px' }}>
167+
{app.published ? <CheckCircleFilled /> : null} {app.published ? 'Published' : 'Draft'}
168+
</Tag>
169+
<Tag
170+
color={app.managed ? 'processing' : 'default'}
171+
style={{ marginTop: 8, borderRadius: '12px' }}
172+
>
173+
{app.managed ? <CloudServerOutlined /> : <DisconnectOutlined />} {app.managed ? 'Managed' : 'Unmanaged'}
174+
</Tag>
175+
</Space>
158176
),
159177
},
160178
{
@@ -165,7 +183,6 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
165183
checked={!!app.managed}
166184
onChange={(checked: boolean) => handleToggleManaged(app, checked)}
167185
loading={refreshing}
168-
size="small"
169186
/>
170187
),
171188
},
@@ -177,7 +194,6 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
177194
<Tooltip title="View Audit Logs">
178195
<Button
179196
icon={<AuditOutlined />}
180-
size="small"
181197
onClick={(e) => {
182198
e.stopPropagation();
183199
const auditUrl = `/setting/audit?environmentId=${environment.environmentId}&orgId=${workspace.id}&appId=${app.applicationId}&pageSize=100&pageNum=1`;
@@ -190,7 +206,6 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
190206
<Tooltip title={!app.managed ? "App must be managed before it can be deployed" : "Deploy this app to another environment"}>
191207
<Button
192208
type="primary"
193-
size="small"
194209
icon={<CloudUploadOutlined />}
195210
onClick={() => openDeployModal(app, appsConfig, environment)}
196211
disabled={!app.managed}
@@ -203,50 +218,86 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
203218
}
204219
];
205220

221+
// Helper function to generate colors from strings
222+
const stringToColor = (str: string) => {
223+
let hash = 0;
224+
for (let i = 0; i < str.length; i++) {
225+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
226+
}
227+
228+
const hue = Math.abs(hash % 360);
229+
return `hsl(${hue}, 70%, 50%)`;
230+
};
231+
232+
// Stat card component
233+
const StatCard = ({ title, value, icon }: { title: string; value: number; icon: React.ReactNode }) => (
234+
<Card
235+
style={{
236+
height: '100%',
237+
borderRadius: '8px',
238+
boxShadow: '0 2px 8px rgba(0,0,0,0.05)'
239+
}}
240+
>
241+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
242+
<div>
243+
<div style={{ fontSize: '14px', color: '#8c8c8c', marginBottom: '8px' }}>{title}</div>
244+
<div style={{ fontSize: '24px', fontWeight: 600 }}>{value}</div>
245+
</div>
246+
<div style={{
247+
fontSize: '28px',
248+
opacity: 0.8,
249+
color: '#1890ff',
250+
padding: '12px',
251+
backgroundColor: 'rgba(24, 144, 255, 0.1)',
252+
borderRadius: '50%',
253+
display: 'flex',
254+
alignItems: 'center',
255+
justifyContent: 'center'
256+
}}>
257+
{icon}
258+
</div>
259+
</div>
260+
</Card>
261+
);
262+
206263
return (
207-
<Card>
208-
{/* Header with refresh button */}
209-
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }}>
210-
<Title level={5}>Apps in this Workspace</Title>
264+
<div style={{ padding: '16px 0' }}>
265+
{/* Header */}
266+
<div style={{
267+
display: "flex",
268+
justifyContent: "space-between",
269+
alignItems: "center",
270+
marginBottom: "24px",
271+
background: 'linear-gradient(135deg, #1890ff 0%, #722ed1 100%)',
272+
padding: '20px 24px',
273+
borderRadius: '8px',
274+
color: 'white'
275+
}}>
276+
<div>
277+
<Title level={4} style={{ color: 'white', margin: 0 }}>
278+
<AppstoreOutlined style={{ marginRight: 10 }} /> Apps
279+
</Title>
280+
<p style={{ marginBottom: 0 }}>Manage your workspace applications</p>
281+
</div>
211282
<Button
212283
icon={<SyncOutlined spin={refreshing} />}
213284
onClick={handleRefresh}
214285
loading={loading}
286+
type="primary"
287+
ghost
215288
>
216289
Refresh
217290
</Button>
218291
</div>
219292

220-
{/* Stats display */}
221-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '24px', marginBottom: '16px' }}>
222-
<div>
223-
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Total Apps</div>
224-
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.total}</div>
225-
</div>
226-
<div>
227-
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Published Apps</div>
228-
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.published}</div>
229-
</div>
230-
<div>
231-
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Managed Apps</div>
232-
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.managed}</div>
233-
</div>
234-
<div>
235-
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Unmanaged Apps</div>
236-
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.unmanaged}</div>
237-
</div>
238-
</div>
239-
240-
<Divider style={{ margin: "16px 0" }} />
241-
242293
{/* Error display */}
243294
{error && (
244295
<Alert
245296
message="Error loading apps"
246297
description={error}
247298
type="error"
248299
showIcon
249-
style={{ marginBottom: "16px" }}
300+
style={{ marginBottom: "20px" }}
250301
/>
251302
)}
252303

@@ -257,49 +308,95 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
257308
description="Missing required configuration: API key or API service URL"
258309
type="warning"
259310
showIcon
260-
style={{ marginBottom: "16px" }}
311+
style={{ marginBottom: "20px" }}
261312
/>
262313
)}
263314

315+
{/* Stats display */}
316+
<Row gutter={[16, 16]} style={{ marginBottom: '24px' }}>
317+
<Col xs={12} sm={12} md={6}>
318+
<StatCard
319+
title="Total Apps"
320+
value={stats.total}
321+
icon={<AppstoreOutlined />}
322+
/>
323+
</Col>
324+
<Col xs={12} sm={12} md={6}>
325+
<StatCard
326+
title="Published Apps"
327+
value={stats.published}
328+
icon={<CheckCircleFilled />}
329+
/>
330+
</Col>
331+
<Col xs={12} sm={12} md={6}>
332+
<StatCard
333+
title="Managed Apps"
334+
value={stats.managed}
335+
icon={<CloudServerOutlined />}
336+
/>
337+
</Col>
338+
<Col xs={12} sm={12} md={6}>
339+
<StatCard
340+
title="Unmanaged Apps"
341+
value={stats.unmanaged}
342+
icon={<DisconnectOutlined />}
343+
/>
344+
</Col>
345+
</Row>
346+
264347
{/* Content */}
265-
{loading ? (
266-
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
267-
<Spin tip="Loading apps..." />
268-
</div>
269-
) : apps.length === 0 ? (
270-
<Empty
271-
description={error || "No apps found in this workspace"}
272-
image={Empty.PRESENTED_IMAGE_SIMPLE}
273-
/>
274-
) : (
275-
<>
276-
{/* Search Bar */}
277-
<div style={{ marginBottom: 16 }}>
278-
<Search
279-
placeholder="Search apps by name or ID"
280-
allowClear
281-
onSearch={value => setSearchText(value)}
282-
onChange={e => setSearchText(e.target.value)}
283-
style={{ width: 300 }}
284-
/>
285-
{searchText && filteredApps.length !== apps.length && (
286-
<div style={{ marginTop: 8 }}>
287-
Showing {filteredApps.length} of {apps.length} apps
288-
</div>
289-
)}
348+
<Card
349+
style={{
350+
borderRadius: '8px',
351+
boxShadow: '0 2px 8px rgba(0,0,0,0.05)'
352+
}}
353+
>
354+
{loading ? (
355+
<div style={{ display: 'flex', justifyContent: 'center', padding: '40px' }}>
356+
<Spin size="large" tip="Loading apps..." />
290357
</div>
291-
292-
<Table
293-
columns={columns}
294-
dataSource={filteredApps}
295-
rowKey="applicationId"
296-
pagination={{ pageSize: 10 }}
297-
size="middle"
298-
scroll={{ x: 'max-content' }}
358+
) : apps.length === 0 ? (
359+
<Empty
360+
description={error || "No apps found in this workspace"}
361+
image={Empty.PRESENTED_IMAGE_SIMPLE}
299362
/>
300-
</>
301-
)}
302-
</Card>
363+
) : (
364+
<>
365+
{/* Search Bar */}
366+
<div style={{ marginBottom: 20 }}>
367+
<Search
368+
placeholder="Search apps by name or ID"
369+
allowClear
370+
onSearch={value => setSearchText(value)}
371+
onChange={e => setSearchText(e.target.value)}
372+
style={{ width: 300 }}
373+
size="large"
374+
/>
375+
{searchText && filteredApps.length !== apps.length && (
376+
<div style={{ marginTop: 8, color: '#8c8c8c' }}>
377+
Showing {filteredApps.length} of {apps.length} apps
378+
</div>
379+
)}
380+
</div>
381+
382+
<Table
383+
columns={columns}
384+
dataSource={filteredApps}
385+
rowKey="applicationId"
386+
pagination={{
387+
pageSize: 10,
388+
showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} apps`
389+
}}
390+
rowClassName={() => 'app-row'}
391+
style={{
392+
borderRadius: '8px',
393+
overflow: 'hidden'
394+
}}
395+
/>
396+
</>
397+
)}
398+
</Card>
399+
</div>
303400
);
304401
};
305402

0 commit comments

Comments
 (0)