Skip to content

Commit 2d6ec6b

Browse files
committed
setup workspace detail page
1 parent c66a048 commit 2d6ec6b

File tree

6 files changed

+625
-6
lines changed

6 files changed

+625
-6
lines changed

client/packages/lowcoder/src/pages/setting/environments/Environments.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,27 @@ import React from "react";
33
import { Switch, Route, useRouteMatch } from "react-router-dom";
44
import EnvironmentsList from "./EnvironmentsList"; // Rename your current component
55
import EnvironmentDetail from "./EnvironmentDetail";
6+
import WorkspaceDetail from "./WorkspaceDetail";
7+
import { ENVIRONMENT_WORKSPACE_DETAIL } from "@lowcoder-ee/constants/routesURL";
68

7-
import { ENVIRONMENT_SETTING, ENVIRONMENT_DETAIL } from "@lowcoder-ee/constants/routesURL";
8-
9+
import {
10+
ENVIRONMENT_SETTING,
11+
ENVIRONMENT_DETAIL,
12+
} from "@lowcoder-ee/constants/routesURL";
913

1014
const Environments: React.FC = () => {
1115
return (
1216
<Switch>
13-
<Route exact path={ENVIRONMENT_SETTING}>
14-
<EnvironmentsList />
17+
<Route path={ENVIRONMENT_WORKSPACE_DETAIL}>
18+
<WorkspaceDetail />
1519
</Route>
20+
1621
<Route path={ENVIRONMENT_DETAIL}>
1722
<EnvironmentDetail />
1823
</Route>
19-
24+
<Route exact path={ENVIRONMENT_SETTING}>
25+
<EnvironmentsList />
26+
</Route>
2027
</Switch>
2128
);
2229
};
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import React, { useEffect, useState } from "react";
2+
import { useParams, useHistory } from "react-router-dom";
3+
import history from "@lowcoder-ee/util/history";
4+
import { useWorkspaceDetail } from "./hooks/useWorkspaceDetail";
5+
6+
7+
import {
8+
Spin,
9+
Typography,
10+
Card,
11+
Row,
12+
Col,
13+
Tabs,
14+
Alert,
15+
Button,
16+
Statistic,
17+
Divider,
18+
Breadcrumb
19+
} from "antd";
20+
import {
21+
ReloadOutlined,
22+
AppstoreOutlined,
23+
DatabaseOutlined,
24+
CodeOutlined,
25+
HomeOutlined,
26+
TeamOutlined,
27+
SyncOutlined,
28+
ArrowLeftOutlined
29+
} from "@ant-design/icons";
30+
import { getEnvironmentById } from './services/environments.service';
31+
import AppsList from './components/AppsList';
32+
import { Workspace } from './types/workspace.types';
33+
import { Environment } from './types/environment.types';
34+
import { App } from './types/app.types';
35+
36+
const { Title, Text } = Typography;
37+
const { TabPane } = Tabs;
38+
39+
40+
const WorkspaceDetail: React.FC = () => {
41+
// Get parameters from URL
42+
const { environmentId, workspaceId } = useParams<{
43+
environmentId: string;
44+
workspaceId: string;
45+
}>();
46+
47+
// Use the custom hook
48+
const {
49+
environment,
50+
workspace,
51+
apps,
52+
appsLoading,
53+
appsError,
54+
refreshApps,
55+
appStats,
56+
isLoading,
57+
hasError
58+
} = useWorkspaceDetail(environmentId, workspaceId);
59+
60+
// Handle loading/error states
61+
if (isLoading) {
62+
return <Spin />;
63+
}
64+
65+
// Handle loading/error states
66+
if (isLoading) {
67+
return (
68+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%', padding: '50px' }}>
69+
<Spin size="large" tip="Loading workspace details..." />
70+
</div>
71+
);
72+
}
73+
74+
// Handle error state
75+
if (hasError || !environment || !workspace) {
76+
return (
77+
<Alert
78+
message="Error loading workspace details"
79+
description="Could not load the workspace or environment data. Please try again."
80+
type="error"
81+
showIcon
82+
style={{ margin: '24px' }}
83+
action={
84+
<Button type="primary" onClick={() => history.push(`/home/settings/environments/${environmentId}`)}>
85+
Back to Environment
86+
</Button>
87+
}
88+
/>
89+
);
90+
}
91+
92+
return (
93+
<div className="workspace-detail-container" style={{ padding: '24px' }}>
94+
{/* Breadcrumb navigation */}
95+
<Breadcrumb style={{ marginBottom: '16px' }}>
96+
<Breadcrumb.Item onClick={() => history.push('/home/settings/environments')}>
97+
<HomeOutlined /> Environments
98+
</Breadcrumb.Item>
99+
<Breadcrumb.Item onClick={() => history.push(`/home/settings/environments/${environmentId}`)}>
100+
<TeamOutlined /> {environment.environmentName}
101+
</Breadcrumb.Item>
102+
<Breadcrumb.Item>
103+
{workspace.name}
104+
</Breadcrumb.Item>
105+
</Breadcrumb>
106+
107+
{/* Header with workspace name and controls */}
108+
<div className="workspace-header" style={{ marginBottom: '24px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
109+
<div>
110+
<Button
111+
type="link"
112+
icon={<ArrowLeftOutlined />}
113+
onClick={() => history.push(`/home/settings/environments/${environmentId}`)}
114+
style={{ marginLeft: -12, marginRight: 8 }}
115+
>
116+
Back to Environment
117+
</Button>
118+
<Title level={3}>{workspace.name}</Title>
119+
<Text type="secondary">ID: {workspace.id}</Text>
120+
</div>
121+
</div>
122+
123+
{/* Tabs for Apps, Data Sources, and Queries */}
124+
<Tabs defaultActiveKey="apps">
125+
<TabPane
126+
tab={<span><AppstoreOutlined /> Apps</span>}
127+
key="apps"
128+
>
129+
<Card>
130+
{/* Header with refresh button */}
131+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
132+
<Title level={5}>Apps in this Workspace</Title>
133+
<Button
134+
icon={<SyncOutlined />}
135+
onClick={refreshApps}
136+
size="small"
137+
loading={appsLoading}
138+
>
139+
Refresh Apps
140+
</Button>
141+
</div>
142+
143+
{/* App Statistics */}
144+
<Row gutter={16} style={{ marginBottom: '24px' }}>
145+
<Col span={8}>
146+
<Statistic
147+
title="Total Apps"
148+
value={appStats.total}
149+
prefix={<AppstoreOutlined />}
150+
/>
151+
</Col>
152+
<Col span={8}>
153+
<Statistic
154+
title="Published Apps"
155+
value={appStats.published}
156+
prefix={<AppstoreOutlined />}
157+
/>
158+
</Col>
159+
</Row>
160+
161+
<Divider style={{ margin: '16px 0' }} />
162+
163+
{/* Show error if apps loading failed */}
164+
{appsError && (
165+
<Alert
166+
message="Error loading apps"
167+
description={appsError}
168+
type="error"
169+
showIcon
170+
style={{ marginBottom: '16px' }}
171+
action={
172+
<Button size="small" type="primary" onClick={refreshApps}>
173+
Try Again
174+
</Button>
175+
}
176+
/>
177+
)}
178+
179+
{/* Apps List */}
180+
<AppsList
181+
apps={apps}
182+
loading={appsLoading}
183+
error={appsError}
184+
/>
185+
</Card>
186+
</TabPane>
187+
188+
<TabPane
189+
tab={<span><DatabaseOutlined /> Data Sources</span>}
190+
key="dataSources"
191+
>
192+
<Card>
193+
<Alert
194+
message="Data Sources"
195+
description="Data Sources feature will be implemented in the next phase."
196+
type="info"
197+
showIcon
198+
/>
199+
</Card>
200+
</TabPane>
201+
202+
<TabPane
203+
tab={<span><CodeOutlined /> Queries</span>}
204+
key="queries"
205+
>
206+
<Card>
207+
<Alert
208+
message="Queries"
209+
description="Queries feature will be implemented in the next phase."
210+
type="info"
211+
showIcon
212+
/>
213+
</Card>
214+
</TabPane>
215+
</Tabs>
216+
</div>
217+
);
218+
}
219+
220+
221+
export default WorkspaceDetail
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React from 'react';
2+
import { Table, Tag, Empty, Spin, Avatar, Tooltip } from 'antd';
3+
import {
4+
AppstoreOutlined,
5+
UserOutlined,
6+
CheckCircleOutlined,
7+
CloseCircleOutlined
8+
} from '@ant-design/icons';
9+
import { App } from '../types/app.types';
10+
11+
interface AppsListProps {
12+
apps: App[];
13+
loading: boolean;
14+
error?: string | null;
15+
}
16+
17+
/**
18+
* Component to display a list of apps in a table
19+
*/
20+
const AppsList: React.FC<AppsListProps> = ({
21+
apps,
22+
loading,
23+
error,
24+
}) => {
25+
// Format timestamp to date string
26+
const formatDate = (timestamp?: number): string => {
27+
if (!timestamp) return 'N/A';
28+
const date = new Date(timestamp);
29+
return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
30+
};
31+
32+
// Table columns definition
33+
const columns = [
34+
{
35+
title: 'Title',
36+
key: 'title',
37+
render: (record: App) => (
38+
<div style={{ display: 'flex', alignItems: 'center' }}>
39+
<Avatar
40+
size="small"
41+
icon={<AppstoreOutlined />}
42+
src={record.icon || undefined}
43+
style={{ marginRight: 8 }}
44+
/>
45+
<span>{record.title || record.name}</span>
46+
</div>
47+
),
48+
},
49+
{
50+
title: 'Created By',
51+
dataIndex: 'createBy',
52+
key: 'createBy',
53+
render: (createBy: string) => (
54+
<div style={{ display: 'flex', alignItems: 'center' }}>
55+
<Avatar size="small" icon={<UserOutlined />} style={{ marginRight: 8 }} />
56+
<span>{createBy}</span>
57+
</div>
58+
),
59+
},
60+
{
61+
title: 'Created',
62+
key: 'createAt',
63+
render: (record: App) => formatDate(record.createAt),
64+
},
65+
{
66+
title: 'Last Modified',
67+
key: 'lastModifyTime',
68+
render: (record: App) => formatDate(record.lastModifyTime),
69+
},
70+
{
71+
title: 'Published',
72+
dataIndex: 'published',
73+
key: 'published',
74+
render: (published: boolean) => (
75+
<Tooltip title={published ? 'Published' : 'Not Published'}>
76+
{published ?
77+
<CheckCircleOutlined style={{ color: '#52c41a' }} /> :
78+
<CloseCircleOutlined style={{ color: '#f5222d' }} />
79+
}
80+
</Tooltip>
81+
),
82+
},
83+
{
84+
title: 'Status',
85+
dataIndex: 'applicationStatus',
86+
key: 'applicationStatus',
87+
render: (status: string) => (
88+
<Tag color={status === 'NORMAL' ? 'green' : 'red'}>
89+
{status}
90+
</Tag>
91+
),
92+
}
93+
];
94+
95+
// If loading, show spinner
96+
if (loading) {
97+
return (
98+
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
99+
<Spin tip="Loading apps..." />
100+
</div>
101+
);
102+
}
103+
104+
// If no apps or error, show empty state
105+
if (!apps || apps.length === 0 || error) {
106+
return (
107+
<Empty
108+
description={error || "No apps found"}
109+
image={Empty.PRESENTED_IMAGE_SIMPLE}
110+
/>
111+
);
112+
}
113+
114+
return (
115+
<Table
116+
columns={columns}
117+
dataSource={apps}
118+
rowKey="applicationId"
119+
pagination={{ pageSize: 10 }}
120+
size="middle"
121+
/>
122+
);
123+
};
124+
125+
export default AppsList;

0 commit comments

Comments
 (0)