Skip to content

Commit 23b8462

Browse files
committed
add user groups tab generic
1 parent 10b7140 commit 23b8462

File tree

4 files changed

+195
-24
lines changed

4 files changed

+195
-24
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { useEnvironmentContext } from "./context/EnvironmentContext";
2222
import WorkspacesTab from "./components/WorkspacesTab";
2323
import UserGroupsTab from "./components/UserGroupsTab";
2424
import { workspaceConfig } from "./config/workspace.config";
25+
import { userGroupsConfig } from "./config/usergroups.config";
2526
import DeployableItemsTab from "./components/DeployableItemsTab";
2627

2728

@@ -173,7 +174,13 @@ const EnvironmentDetail: React.FC = () => {
173174
}
174175
key="userGroups"
175176
>
176-
<UserGroupsTab environment={environment} />
177+
{/* Using our new generic component with the user group config */}
178+
<DeployableItemsTab
179+
environment={environment}
180+
config={userGroupsConfig}
181+
title="User Groups in this Environment"
182+
/>
183+
177184
</TabPane>
178185
</Tabs>
179186
</div>

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

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,20 @@ function DeployableItemsList<T extends DeployableItem, S extends BaseStats>({
2727
additionalParams = {}
2828
}: DeployableItemsListProps<T, S>) {
2929
// Handle row click for navigation
30-
const handleRowClick = (item: T) => {
31-
// Build the route using the config and navigate
32-
const route = config.buildDetailRoute({
33-
environmentId: environment.environmentId,
34-
itemId: item[config.idField] as string,
35-
...additionalParams
36-
});
37-
38-
history.push(route);
39-
};
30+
// Handle row click for navigation
31+
const handleRowClick = (item: T) => {
32+
// Skip navigation if the route is just '#' (for non-navigable items)
33+
if (config.buildDetailRoute({}) === '#') return;
34+
35+
// Build the route using the config and navigate
36+
const route = config.buildDetailRoute({
37+
environmentId: environment.environmentId,
38+
itemId: item[config.idField] as string,
39+
...additionalParams
40+
});
41+
42+
history.push(route);
43+
};
4044

4145
// Generate columns based on config
4246
let columns = [...config.columns];
@@ -85,18 +89,21 @@ function DeployableItemsList<T extends DeployableItem, S extends BaseStats>({
8589
);
8690
}
8791

92+
const hasNavigation = config.buildDetailRoute({}) !== '#';
93+
94+
8895
return (
8996
<Table
90-
columns={columns}
91-
dataSource={items}
92-
rowKey={config.idField}
93-
pagination={{ pageSize: 10 }}
94-
size="middle"
95-
onRow={(record) => ({
96-
onClick: () => handleRowClick(record),
97-
style: { cursor: 'pointer' },
98-
})}
99-
/>
97+
columns={columns}
98+
dataSource={items}
99+
rowKey={config.idField}
100+
pagination={{ pageSize: 10 }}
101+
size="middle"
102+
onRow={(record) => ({
103+
onClick: hasNavigation ? () => handleRowClick(record) : undefined,
104+
style: hasNavigation ? { cursor: 'pointer' } : undefined,
105+
})}
106+
/>
100107
);
101108
}
102109

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// config/usergroups.config.tsx
2+
import React from 'react';
3+
import { Row, Col, Statistic, Tag, Badge } from 'antd';
4+
import { TeamOutlined, UserOutlined } from '@ant-design/icons';
5+
import { getEnvironmentUserGroups } from '../services/environments.service';
6+
import { UserGroup, UserGroupStats } from '../types/userGroup.types';
7+
import { DeployableItemConfig } from '../types/deployable-item.types';
8+
9+
10+
const formatDate = (timestamp: number): string => {
11+
if (!timestamp) return 'N/A';
12+
const date = new Date(timestamp);
13+
return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
14+
};
15+
16+
17+
export const userGroupsConfig: DeployableItemConfig<UserGroup, UserGroupStats> = {
18+
// Basic info
19+
type: 'userGroups',
20+
singularLabel: 'User Group',
21+
pluralLabel: 'User Groups',
22+
icon: <TeamOutlined />,
23+
idField: 'id',
24+
25+
// Navigation - No navigation for user groups, provide a dummy function
26+
buildDetailRoute: () => '#',
27+
28+
// Configuration
29+
requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'],
30+
31+
// Stats rendering - Custom for user groups
32+
renderStats: (stats) => (
33+
<Row gutter={16}>
34+
<Col span={8}>
35+
<Statistic title="Total User Groups" value={stats.total} prefix={<TeamOutlined />} />
36+
</Col>
37+
<Col span={8}>
38+
<Statistic title="Total Users" value={stats.totalUsers} prefix={<UserOutlined />} />
39+
</Col>
40+
<Col span={8}>
41+
<Statistic title="Admin Users" value={stats.adminUsers} prefix={<UserOutlined />} />
42+
</Col>
43+
</Row>
44+
),
45+
46+
// Stats calculation - Custom for user groups
47+
calculateStats: (userGroups) => {
48+
const total = userGroups.length;
49+
const totalUsers = userGroups.reduce(
50+
(sum, group) => sum + (group.stats?.userCount ?? 0),
51+
0
52+
);
53+
const adminUsers = userGroups.reduce(
54+
(sum, group) => sum + (group.stats?.adminUserCount ?? 0),
55+
0
56+
);
57+
58+
return {
59+
total,
60+
managed: 0, // User groups don't have managed/unmanaged state
61+
unmanaged: 0, // User groups don't have managed/unmanaged state
62+
totalUsers,
63+
adminUsers
64+
};
65+
},
66+
67+
// Table configuration
68+
columns: [
69+
{
70+
title: 'Name',
71+
dataIndex: 'groupName',
72+
key: 'groupName',
73+
render: (name: string, record: UserGroup) => (
74+
<div>
75+
<span>{record.groupName}</span>
76+
{record.allUsersGroup && (
77+
<Tag color="blue" style={{ marginLeft: 8 }}>All Users</Tag>
78+
)}
79+
{record.devGroup && (
80+
<Tag color="orange" style={{ marginLeft: 8 }}>Dev</Tag>
81+
)}
82+
</div>
83+
),
84+
},
85+
{
86+
title: 'ID',
87+
dataIndex: 'groupId',
88+
key: 'groupId',
89+
ellipsis: true,
90+
},
91+
{
92+
title: 'Users',
93+
key: 'userCount',
94+
render: (_, record: UserGroup) => (
95+
<div>
96+
<Badge count={record.stats.userCount} showZero style={{ backgroundColor: '#52c41a' }} />
97+
<span style={{ marginLeft: 8 }}>
98+
({record.stats.adminUserCount} admin{record.stats.adminUserCount !== 1 ? 's' : ''})
99+
</span>
100+
</div>
101+
),
102+
},
103+
{
104+
title: 'Created',
105+
key: 'createTime',
106+
render: (_, record: UserGroup) => formatDate(record.createTime),
107+
},
108+
{
109+
title: 'Type',
110+
key: 'type',
111+
render: (_, record: UserGroup) => {
112+
if (record.allUsersGroup) return <Tag color="blue">Global</Tag>;
113+
if (record.devGroup) return <Tag color="orange">Dev</Tag>;
114+
if (record.syncGroup) return <Tag color="purple">Sync</Tag>;
115+
return <Tag color="default">Standard</Tag>;
116+
},
117+
}
118+
],
119+
120+
// No managed status for user groups
121+
enableManaged: false,
122+
123+
// Service functions
124+
fetchItems: async ({ environment }) => {
125+
const userGroups = await getEnvironmentUserGroups(
126+
environment.environmentId,
127+
environment.environmentApikey,
128+
environment.environmentApiServiceUrl!
129+
);
130+
131+
// Map the required properties to satisfy DeployableItem interface
132+
return userGroups.map(group => ({
133+
...group,
134+
id: group.groupId, // Map groupId to id
135+
name: group.groupName // Map groupName to name
136+
}));
137+
},
138+
139+
// Dummy function for toggleManaged (will never be called since enableManaged is false)
140+
toggleManaged: async () => {
141+
return false;
142+
}
143+
};

client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
/**
22
* Represents a User Group entity in an environment
3-
*/
4-
export interface UserGroup {
3+
*/
4+
5+
import { DeployableItem, BaseStats } from "./deployable-item.types";
6+
7+
export interface UserGroup extends DeployableItem {
58
groupId: string;
69
groupGid: string;
710
groupName: string;
@@ -17,4 +20,15 @@ export interface UserGroup {
1720
syncDelete: boolean;
1821
devGroup: boolean;
1922
syncGroup: boolean;
20-
}
23+
id: string;
24+
name: string;
25+
}
26+
27+
28+
/**
29+
* Statistics for User Groups
30+
*/
31+
export interface UserGroupStats extends BaseStats {
32+
totalUsers: number;
33+
adminUsers: number;
34+
}

0 commit comments

Comments
 (0)