@micha.bigler/ui-core-micha 2.2.7 → 2.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,11 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { Box, Typography, FormControl, Select, MenuItem, Button, Tooltip, CircularProgress, Alert, TextField, } from '@mui/material';
|
|
4
4
|
import { DataGrid } from '@mui/x-data-grid';
|
|
5
|
+
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
6
|
+
import CancelIcon from '@mui/icons-material/Cancel';
|
|
5
7
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
6
8
|
import { useTranslation } from 'react-i18next';
|
|
7
9
|
import { fetchUsersList, deleteUser, updateUserRole } from '../auth/authApi';
|
|
8
10
|
const DEFAULT_ROLES = ['none', 'student', 'teacher', 'admin'];
|
|
9
|
-
export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraColumns = [], extraRowActions = [], extraContext = null, refreshTrigger = 0, canEditUser, }) {
|
|
11
|
+
export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraColumns = [], extraRowActions = [], extraContext = null, refreshTrigger = 0, canEditUser, showNewColumn = true, showSuccessfulLoginColumn = true, showRoleColumn = true, onChangeRole = null, showDeleteAction = true, canDeleteUser = null, onDeleteUser = null, }) {
|
|
10
12
|
const { t } = useTranslation();
|
|
11
13
|
const [users, setUsers] = useState([]);
|
|
12
14
|
const [loading, setLoading] = useState(true);
|
|
@@ -44,7 +46,12 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
44
46
|
if (!window.confirm(t('UserList.DELETE_CONFIRM', 'Are you sure you want to delete this user?')))
|
|
45
47
|
return;
|
|
46
48
|
try {
|
|
47
|
-
|
|
49
|
+
if (typeof onDeleteUser === 'function') {
|
|
50
|
+
await onDeleteUser({ userId, currentUser, extraContext, t, reloadUsers: loadUsers });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
await deleteUser(userId);
|
|
54
|
+
}
|
|
48
55
|
setUsers(prev => prev.filter(u => u.id !== userId));
|
|
49
56
|
}
|
|
50
57
|
catch (err) {
|
|
@@ -53,7 +60,19 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
53
60
|
};
|
|
54
61
|
const handleChangeRole = async (userId, newRole) => {
|
|
55
62
|
try {
|
|
56
|
-
|
|
63
|
+
if (typeof onChangeRole === 'function') {
|
|
64
|
+
await onChangeRole({
|
|
65
|
+
userId,
|
|
66
|
+
newRole,
|
|
67
|
+
currentUser,
|
|
68
|
+
extraContext,
|
|
69
|
+
t,
|
|
70
|
+
reloadUsers: loadUsers,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
await updateUserRole(userId, newRole);
|
|
75
|
+
}
|
|
57
76
|
await loadUsers();
|
|
58
77
|
}
|
|
59
78
|
catch (err) {
|
|
@@ -84,6 +103,12 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
84
103
|
}
|
|
85
104
|
return defaultCanEdit(targetUser);
|
|
86
105
|
};
|
|
106
|
+
const canDelete = (targetUser) => {
|
|
107
|
+
if (typeof canDeleteUser === 'function') {
|
|
108
|
+
return Boolean(canDeleteUser({ targetUser, currentUser, extraContext }));
|
|
109
|
+
}
|
|
110
|
+
return canEdit(targetUser);
|
|
111
|
+
};
|
|
87
112
|
const listContext = useMemo(() => ({
|
|
88
113
|
currentUser,
|
|
89
114
|
extraContext,
|
|
@@ -98,6 +123,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
98
123
|
}
|
|
99
124
|
return (user === null || user === void 0 ? void 0 : user.username) || '';
|
|
100
125
|
};
|
|
126
|
+
const renderBooleanStatusIcon = (value, positiveLabel, negativeLabel) => (_jsx(Tooltip, { title: value ? positiveLabel : negativeLabel, children: _jsx("span", { children: value ? (_jsx(CheckCircleIcon, { color: "success", fontSize: "small" })) : (_jsx(CancelIcon, { color: "error", fontSize: "small" })) }) }));
|
|
101
127
|
const getExtraColumnSortValue = (column, user) => {
|
|
102
128
|
var _a;
|
|
103
129
|
const valueContext = { user, currentUser, extraContext, t };
|
|
@@ -184,45 +210,70 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
184
210
|
{
|
|
185
211
|
field: 'email',
|
|
186
212
|
headerName: t('Auth.EMAIL_LABEL', 'Email'),
|
|
187
|
-
minWidth:
|
|
188
|
-
|
|
213
|
+
minWidth: 220,
|
|
214
|
+
maxWidth: 340,
|
|
215
|
+
flex: 1,
|
|
189
216
|
},
|
|
190
217
|
{
|
|
191
218
|
field: 'name',
|
|
192
219
|
headerName: t('Profile.NAME_LABEL', 'Name'),
|
|
193
|
-
minWidth:
|
|
194
|
-
|
|
220
|
+
minWidth: 180,
|
|
221
|
+
maxWidth: 260,
|
|
222
|
+
flex: 0.9,
|
|
195
223
|
valueGetter: (_value, row) => getUserDisplayName(row),
|
|
196
224
|
},
|
|
197
|
-
|
|
225
|
+
];
|
|
226
|
+
if (showNewColumn) {
|
|
227
|
+
baseColumns.push({
|
|
228
|
+
field: 'is_new',
|
|
229
|
+
headerName: t('UserList.NEW', 'New'),
|
|
230
|
+
minWidth: 90,
|
|
231
|
+
maxWidth: 110,
|
|
232
|
+
flex: 0.35,
|
|
233
|
+
align: 'center',
|
|
234
|
+
headerAlign: 'center',
|
|
235
|
+
valueGetter: (_value, row) => { var _a; return Boolean(((_a = row === null || row === void 0 ? void 0 : row.profile) === null || _a === void 0 ? void 0 : _a.is_new) || (row === null || row === void 0 ? void 0 : row.is_new)); },
|
|
236
|
+
renderCell: (params) => {
|
|
237
|
+
var _a, _b, _c;
|
|
238
|
+
return renderBooleanStatusIcon(Boolean(((_b = (_a = params.row) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.is_new) || ((_c = params.row) === null || _c === void 0 ? void 0 : _c.is_new)), t('Common.YES', 'Yes'), t('Common.NO', 'No'));
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (showSuccessfulLoginColumn) {
|
|
243
|
+
baseColumns.push({
|
|
198
244
|
field: 'successful_login',
|
|
199
245
|
headerName: t('UserList.SUCCESSFUL_LOGIN', 'Successful Login'),
|
|
200
|
-
minWidth:
|
|
201
|
-
|
|
246
|
+
minWidth: 120,
|
|
247
|
+
maxWidth: 150,
|
|
248
|
+
flex: 0.4,
|
|
249
|
+
align: 'center',
|
|
250
|
+
headerAlign: 'center',
|
|
202
251
|
valueGetter: (_value, row) => { var _a; return Boolean((_a = row === null || row === void 0 ? void 0 : row.successful_login) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.last_login); },
|
|
203
252
|
renderCell: (params) => {
|
|
204
253
|
var _a, _b, _c;
|
|
205
|
-
return (
|
|
206
|
-
? t('Common.YES', 'Yes')
|
|
207
|
-
: t('Common.NO', 'No') }));
|
|
254
|
+
return renderBooleanStatusIcon(Boolean((_b = (_a = params.row) === null || _a === void 0 ? void 0 : _a.successful_login) !== null && _b !== void 0 ? _b : (_c = params.row) === null || _c === void 0 ? void 0 : _c.last_login), t('Common.YES', 'Yes'), t('Common.NO', 'No'));
|
|
208
255
|
},
|
|
209
|
-
}
|
|
210
|
-
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (showRoleColumn) {
|
|
259
|
+
baseColumns.push({
|
|
211
260
|
field: 'role',
|
|
212
261
|
headerName: t('UserList.ROLE', 'Role'),
|
|
213
262
|
minWidth: 180,
|
|
214
|
-
|
|
263
|
+
maxWidth: 240,
|
|
264
|
+
flex: 0.7,
|
|
215
265
|
valueGetter: (_value, row) => (row === null || row === void 0 ? void 0 : row.role) || 'none',
|
|
216
266
|
renderCell: (params) => {
|
|
217
267
|
const user = params.row;
|
|
218
268
|
return (_jsx(FormControl, { size: "small", fullWidth: true, sx: controlSx, disabled: !canEdit(user), children: _jsx(Select, { value: user.role || 'none', onChange: (event) => handleChangeRole(user.id, event.target.value), variant: "outlined", children: roles.map((role) => _jsx(MenuItem, { value: role, children: role }, role)) }) }));
|
|
219
269
|
},
|
|
220
|
-
}
|
|
221
|
-
|
|
270
|
+
});
|
|
271
|
+
}
|
|
222
272
|
const mappedExtraColumns = visibleExtraColumns.map((column) => ({
|
|
223
273
|
field: `extra:${column.key}`,
|
|
224
274
|
headerName: typeof column.label === 'function' ? column.label(listContext) : column.label,
|
|
225
275
|
minWidth: Number(column.minWidth) || 180,
|
|
276
|
+
maxWidth: Number(column.maxWidth) || 320,
|
|
226
277
|
flex: Number(column.flex) || 0.9,
|
|
227
278
|
sortable: column.sortable !== false,
|
|
228
279
|
align: column.align || 'left',
|
|
@@ -237,11 +288,16 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
237
288
|
reloadUsers: loadUsers,
|
|
238
289
|
}),
|
|
239
290
|
}));
|
|
291
|
+
const hasActionColumn = showDeleteAction || visibleRowActions.length > 0;
|
|
292
|
+
if (!hasActionColumn) {
|
|
293
|
+
return [...baseColumns, ...mappedExtraColumns];
|
|
294
|
+
}
|
|
240
295
|
const actionColumn = {
|
|
241
296
|
field: 'actions',
|
|
242
297
|
headerName: t('Common.ACTIONS', 'Actions'),
|
|
243
|
-
minWidth: Math.max(220, 110 + visibleRowActions.length * 110),
|
|
244
|
-
|
|
298
|
+
minWidth: Math.max(220, 110 + (visibleRowActions.length + (showDeleteAction ? 1 : 0)) * 110),
|
|
299
|
+
maxWidth: Math.max(360, 120 + (visibleRowActions.length + (showDeleteAction ? 1 : 0)) * 120),
|
|
300
|
+
flex: 1.1,
|
|
245
301
|
sortable: false,
|
|
246
302
|
filterable: false,
|
|
247
303
|
disableColumnMenu: true,
|
|
@@ -262,13 +318,17 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
262
318
|
return (_jsx(Button, { size: "small", variant: "outlined", onClick: () => runRowAction(action, user), disabled: isBusy || isDisabled, sx: actionButtonSx, children: typeof action.label === 'function'
|
|
263
319
|
? action.label({ user, t, currentUser, canEdit: canEdit(user) })
|
|
264
320
|
: action.label }, `${action.key}-${user.id}`));
|
|
265
|
-
}), _jsx(Tooltip, { title: t('Common.DELETE', 'Delete'), children: _jsx("span", { children: _jsx(Button, { size: "small", variant: "outlined", color: "error", startIcon: _jsx(DeleteIcon, {}), onClick: () => handleDelete(user.id), disabled: !
|
|
321
|
+
}), showDeleteAction && (_jsx(Tooltip, { title: t('Common.DELETE', 'Delete'), children: _jsx("span", { children: _jsx(Button, { size: "small", variant: "outlined", color: "error", startIcon: _jsx(DeleteIcon, {}), onClick: () => handleDelete(user.id), disabled: !canDelete(user), sx: actionButtonSx, children: t('Common.DELETE', 'Delete') }) }) }))] }));
|
|
266
322
|
},
|
|
267
323
|
};
|
|
268
324
|
return [...baseColumns, ...mappedExtraColumns, actionColumn];
|
|
269
325
|
}, [
|
|
270
326
|
t,
|
|
271
327
|
roles,
|
|
328
|
+
showNewColumn,
|
|
329
|
+
showSuccessfulLoginColumn,
|
|
330
|
+
showRoleColumn,
|
|
331
|
+
showDeleteAction,
|
|
272
332
|
visibleExtraColumns,
|
|
273
333
|
visibleRowActions,
|
|
274
334
|
listContext,
|
|
@@ -277,6 +337,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
277
337
|
extraContext,
|
|
278
338
|
loadUsers,
|
|
279
339
|
canEdit,
|
|
340
|
+
canDelete,
|
|
280
341
|
]);
|
|
281
342
|
return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('UserList.TITLE', 'All Users') }), _jsx(Box, { sx: { mb: 2, maxWidth: 420 }, children: _jsx(TextField, { fullWidth: true, size: "small", label: t('Common.SEARCH', 'Search'), placeholder: t('UserList.SEARCH_PLACEHOLDER', 'Search users...'), value: searchQuery, onChange: (event) => setSearchQuery(event.target.value) }) }), error && _jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(error) }), loading && (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 3 }, children: _jsx(CircularProgress, {}) })), !loading && (_jsx(Box, { sx: { width: '100%', minHeight: 520 }, children: _jsx(DataGrid, { rows: filteredUsers, columns: columns, disableRowSelectionOnClick: true, showToolbar: true, getRowHeight: () => 'auto', pageSizeOptions: [10, 25, 50, 100], initialState: {
|
|
282
343
|
sorting: { sortModel: [{ field: 'email', sort: 'asc' }] },
|
|
@@ -21,7 +21,7 @@ import { QrSignupValidityManager } from '../components/QrSignupValidityManager';
|
|
|
21
21
|
import { SupportRecoveryRequestsTab } from '../components/SupportRecoveryRequestsTab';
|
|
22
22
|
import { BulkInviteCsvTab } from '../components/BulkInviteCsvTab';
|
|
23
23
|
import { fetchAuthPolicy, updateUserProfile } from '../auth/authApi'; // Ggf. Pfad anpassen
|
|
24
|
-
export function AccountPage({ userListExtraColumns = [], userListExtraRowActions = [], userListExtraContext = null, userListRefreshTrigger = 0, userListCanEditUser = null, showBulkInviteCsvTab = false, bulkInviteCsvProps = {}, extraTabs = [], }) {
|
|
24
|
+
export function AccountPage({ userListExtraColumns = [], userListExtraRowActions = [], userListExtraContext = null, userListRefreshTrigger = 0, userListCanEditUser = null, userListShowNewColumn = true, userListShowSuccessfulLoginColumn = true, userListShowRoleColumn = true, userListOnChangeRole = null, userListShowDeleteAction = true, userListCanDeleteUser = null, userListOnDeleteUser = null, showBulkInviteCsvTab = false, bulkInviteCsvProps = {}, extraTabs = [], }) {
|
|
25
25
|
var _a;
|
|
26
26
|
const { t } = useTranslation();
|
|
27
27
|
const { user, login, loading } = useContext(AuthContext);
|
|
@@ -129,5 +129,5 @@ export function AccountPage({ userListExtraColumns = [], userListExtraRowActions
|
|
|
129
129
|
const activeExtraTab = builtInTabValues.has(safeTab)
|
|
130
130
|
? null
|
|
131
131
|
: extraTabs.find((tab) => tab.value === safeTab);
|
|
132
|
-
return (_jsxs(WidePage, { title: t('Account.TITLE', 'Account & Administration'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('Account.PAGE_TITLE', 'Account'), " \u2013 ", user.email] }) }), _jsx(Tabs, { value: safeTab, onChange: handleTabChange, variant: "scrollable", scrollButtons: "auto", sx: { mb: 3, borderBottom: 1, borderColor: 'divider' }, children: tabs.map((tab) => (_jsx(Tab, { label: tab.label, value: tab.value }, tab.value))) }), safeTab === 'profile' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(ProfileComponent, { onSubmit: handleProfileSubmit, showName: true, showPrivacy: true, showCookies: true }) })), safeTab === 'security' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(SecurityComponent, { fromRecovery: fromRecovery, fromWeakLogin: fromWeakLogin }) })), safeTab === 'users' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(UserListComponent, { roles: activeRoles, currentUser: user, extraColumns: userListExtraColumns, extraRowActions: userListExtraRowActions, extraContext: userListExtraContext, refreshTrigger: userListRefreshTrigger, canEditUser: userListCanEditUser }) })), safeTab === 'invite' && (_jsx(Box, { sx: { mt: 2 }, children: _jsxs(Stack, { spacing: 2.5, children: [canViewAuthPolicy && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(AuthFactorRequirementCard, { canEdit: canWriteAuthPolicy, policy: authPolicy }) })), canViewAuthPolicy && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(RegistrationMethodsManager, { policy: authPolicy, error: authPolicyError, onPolicyChange: setAuthPolicy, canEdit: canWriteAuthPolicy }) })), canSendInvites && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_admin_invite) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(UserInviteComponent, {}) })), canManageAccessCodes && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_access_code) && (_jsxs(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.ACCESS_CODE_MANAGER_TITLE', 'Access Codes') }), _jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Account.ACCESS_CODES_HINT', 'Manage access codes for self-registration.') }), _jsx(AccessCodeManager, {})] })), canSendInvites && showBulkInviteCsvTab && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_admin_invite) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(BulkInviteCsvTab, Object.assign({}, bulkInviteCsvProps)) })), canViewAuthPolicy && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_email_domain) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(AllowedEmailDomainsManager, { enabled: Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_email_domain), domains: (authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allowed_email_domains) || [], onPolicyChange: setAuthPolicy, canEdit: canWriteAuthPolicy }) })), canViewAuthPolicy && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_qr) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(QrSignupValidityManager, { enabled: Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_qr), expiryDays: authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.signup_qr_expiry_days, onPolicyChange: setAuthPolicy, canEdit: canWriteAuthPolicy }) })), canManageSignupQr && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_qr) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(QrSignupManager, { enabled: Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_qr), expiryDays: authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.signup_qr_expiry_days }) }))] }) })), safeTab === 'support' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(SupportRecoveryRequestsTab, {}) })), activeExtraTab && (_jsx(Box, { sx: { mt: 2 }, children: (_a = activeExtraTab.render) === null || _a === void 0 ? void 0 : _a.call(activeExtraTab, { user, perms, isSuperUser, t }) }))] }));
|
|
132
|
+
return (_jsxs(WidePage, { title: t('Account.TITLE', 'Account & Administration'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('Account.PAGE_TITLE', 'Account'), " \u2013 ", user.email] }) }), _jsx(Tabs, { value: safeTab, onChange: handleTabChange, variant: "scrollable", scrollButtons: "auto", sx: { mb: 3, borderBottom: 1, borderColor: 'divider' }, children: tabs.map((tab) => (_jsx(Tab, { label: tab.label, value: tab.value }, tab.value))) }), safeTab === 'profile' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(ProfileComponent, { onSubmit: handleProfileSubmit, showName: true, showPrivacy: true, showCookies: true }) })), safeTab === 'security' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(SecurityComponent, { fromRecovery: fromRecovery, fromWeakLogin: fromWeakLogin }) })), safeTab === 'users' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(UserListComponent, { roles: activeRoles, currentUser: user, extraColumns: userListExtraColumns, extraRowActions: userListExtraRowActions, extraContext: userListExtraContext, refreshTrigger: userListRefreshTrigger, canEditUser: userListCanEditUser, showNewColumn: userListShowNewColumn, showSuccessfulLoginColumn: userListShowSuccessfulLoginColumn, showRoleColumn: userListShowRoleColumn, onChangeRole: userListOnChangeRole, showDeleteAction: userListShowDeleteAction, canDeleteUser: userListCanDeleteUser, onDeleteUser: userListOnDeleteUser }) })), safeTab === 'invite' && (_jsx(Box, { sx: { mt: 2 }, children: _jsxs(Stack, { spacing: 2.5, children: [canViewAuthPolicy && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(AuthFactorRequirementCard, { canEdit: canWriteAuthPolicy, policy: authPolicy }) })), canViewAuthPolicy && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(RegistrationMethodsManager, { policy: authPolicy, error: authPolicyError, onPolicyChange: setAuthPolicy, canEdit: canWriteAuthPolicy }) })), canSendInvites && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_admin_invite) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(UserInviteComponent, {}) })), canManageAccessCodes && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_access_code) && (_jsxs(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.ACCESS_CODE_MANAGER_TITLE', 'Access Codes') }), _jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Account.ACCESS_CODES_HINT', 'Manage access codes for self-registration.') }), _jsx(AccessCodeManager, {})] })), canSendInvites && showBulkInviteCsvTab && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_admin_invite) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(BulkInviteCsvTab, Object.assign({}, bulkInviteCsvProps)) })), canViewAuthPolicy && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_email_domain) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(AllowedEmailDomainsManager, { enabled: Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_email_domain), domains: (authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allowed_email_domains) || [], onPolicyChange: setAuthPolicy, canEdit: canWriteAuthPolicy }) })), canViewAuthPolicy && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_qr) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(QrSignupValidityManager, { enabled: Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_qr), expiryDays: authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.signup_qr_expiry_days, onPolicyChange: setAuthPolicy, canEdit: canWriteAuthPolicy }) })), canManageSignupQr && Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_qr) && (_jsx(Paper, { variant: "outlined", sx: { p: 2.5, borderRadius: 2 }, children: _jsx(QrSignupManager, { enabled: Boolean(authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.allow_self_signup_qr), expiryDays: authPolicy === null || authPolicy === void 0 ? void 0 : authPolicy.signup_qr_expiry_days }) }))] }) })), safeTab === 'support' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(SupportRecoveryRequestsTab, {}) })), activeExtraTab && (_jsx(Box, { sx: { mt: 2 }, children: (_a = activeExtraTab.render) === null || _a === void 0 ? void 0 : _a.call(activeExtraTab, { user, perms, isSuperUser, t }) }))] }));
|
|
133
133
|
}
|
package/package.json
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
TextField,
|
|
13
13
|
} from '@mui/material';
|
|
14
14
|
import { DataGrid } from '@mui/x-data-grid';
|
|
15
|
+
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
16
|
+
import CancelIcon from '@mui/icons-material/Cancel';
|
|
15
17
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
16
18
|
import { useTranslation } from 'react-i18next';
|
|
17
19
|
import { fetchUsersList, deleteUser, updateUserRole } from '../auth/authApi';
|
|
@@ -26,6 +28,13 @@ export function UserListComponent({
|
|
|
26
28
|
extraContext = null,
|
|
27
29
|
refreshTrigger = 0,
|
|
28
30
|
canEditUser,
|
|
31
|
+
showNewColumn = true,
|
|
32
|
+
showSuccessfulLoginColumn = true,
|
|
33
|
+
showRoleColumn = true,
|
|
34
|
+
onChangeRole = null,
|
|
35
|
+
showDeleteAction = true,
|
|
36
|
+
canDeleteUser = null,
|
|
37
|
+
onDeleteUser = null,
|
|
29
38
|
}) {
|
|
30
39
|
const { t } = useTranslation();
|
|
31
40
|
const [users, setUsers] = useState([]);
|
|
@@ -63,7 +72,11 @@ export function UserListComponent({
|
|
|
63
72
|
const handleDelete = async (userId) => {
|
|
64
73
|
if (!window.confirm(t('UserList.DELETE_CONFIRM', 'Are you sure you want to delete this user?'))) return;
|
|
65
74
|
try {
|
|
66
|
-
|
|
75
|
+
if (typeof onDeleteUser === 'function') {
|
|
76
|
+
await onDeleteUser({ userId, currentUser, extraContext, t, reloadUsers: loadUsers });
|
|
77
|
+
} else {
|
|
78
|
+
await deleteUser(userId);
|
|
79
|
+
}
|
|
67
80
|
setUsers(prev => prev.filter(u => u.id !== userId));
|
|
68
81
|
} catch (err) {
|
|
69
82
|
alert(t(err.code || 'Auth.USER_DELETE_FAILED'));
|
|
@@ -72,7 +85,18 @@ export function UserListComponent({
|
|
|
72
85
|
|
|
73
86
|
const handleChangeRole = async (userId, newRole) => {
|
|
74
87
|
try {
|
|
75
|
-
|
|
88
|
+
if (typeof onChangeRole === 'function') {
|
|
89
|
+
await onChangeRole({
|
|
90
|
+
userId,
|
|
91
|
+
newRole,
|
|
92
|
+
currentUser,
|
|
93
|
+
extraContext,
|
|
94
|
+
t,
|
|
95
|
+
reloadUsers: loadUsers,
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
await updateUserRole(userId, newRole);
|
|
99
|
+
}
|
|
76
100
|
await loadUsers();
|
|
77
101
|
} catch (err) {
|
|
78
102
|
alert(t(err.code || 'Auth.USER_ROLE_UPDATE_FAILED'));
|
|
@@ -102,6 +126,13 @@ export function UserListComponent({
|
|
|
102
126
|
return defaultCanEdit(targetUser);
|
|
103
127
|
};
|
|
104
128
|
|
|
129
|
+
const canDelete = (targetUser) => {
|
|
130
|
+
if (typeof canDeleteUser === 'function') {
|
|
131
|
+
return Boolean(canDeleteUser({ targetUser, currentUser, extraContext }));
|
|
132
|
+
}
|
|
133
|
+
return canEdit(targetUser);
|
|
134
|
+
};
|
|
135
|
+
|
|
105
136
|
const listContext = useMemo(() => ({
|
|
106
137
|
currentUser,
|
|
107
138
|
extraContext,
|
|
@@ -132,6 +163,18 @@ export function UserListComponent({
|
|
|
132
163
|
return user?.username || '';
|
|
133
164
|
};
|
|
134
165
|
|
|
166
|
+
const renderBooleanStatusIcon = (value, positiveLabel, negativeLabel) => (
|
|
167
|
+
<Tooltip title={value ? positiveLabel : negativeLabel}>
|
|
168
|
+
<span>
|
|
169
|
+
{value ? (
|
|
170
|
+
<CheckCircleIcon color="success" fontSize="small" />
|
|
171
|
+
) : (
|
|
172
|
+
<CancelIcon color="error" fontSize="small" />
|
|
173
|
+
)}
|
|
174
|
+
</span>
|
|
175
|
+
</Tooltip>
|
|
176
|
+
);
|
|
177
|
+
|
|
135
178
|
const getExtraColumnSortValue = (column, user) => {
|
|
136
179
|
const valueContext = { user, currentUser, extraContext, t };
|
|
137
180
|
|
|
@@ -223,35 +266,63 @@ export function UserListComponent({
|
|
|
223
266
|
{
|
|
224
267
|
field: 'email',
|
|
225
268
|
headerName: t('Auth.EMAIL_LABEL', 'Email'),
|
|
226
|
-
minWidth:
|
|
227
|
-
|
|
269
|
+
minWidth: 220,
|
|
270
|
+
maxWidth: 340,
|
|
271
|
+
flex: 1,
|
|
228
272
|
},
|
|
229
273
|
{
|
|
230
274
|
field: 'name',
|
|
231
275
|
headerName: t('Profile.NAME_LABEL', 'Name'),
|
|
232
|
-
minWidth:
|
|
233
|
-
|
|
276
|
+
minWidth: 180,
|
|
277
|
+
maxWidth: 260,
|
|
278
|
+
flex: 0.9,
|
|
234
279
|
valueGetter: (_value, row) => getUserDisplayName(row),
|
|
235
280
|
},
|
|
236
|
-
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
if (showNewColumn) {
|
|
284
|
+
baseColumns.push({
|
|
285
|
+
field: 'is_new',
|
|
286
|
+
headerName: t('UserList.NEW', 'New'),
|
|
287
|
+
minWidth: 90,
|
|
288
|
+
maxWidth: 110,
|
|
289
|
+
flex: 0.35,
|
|
290
|
+
align: 'center',
|
|
291
|
+
headerAlign: 'center',
|
|
292
|
+
valueGetter: (_value, row) => Boolean(row?.profile?.is_new || row?.is_new),
|
|
293
|
+
renderCell: (params) => renderBooleanStatusIcon(
|
|
294
|
+
Boolean(params.row?.profile?.is_new || params.row?.is_new),
|
|
295
|
+
t('Common.YES', 'Yes'),
|
|
296
|
+
t('Common.NO', 'No'),
|
|
297
|
+
),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (showSuccessfulLoginColumn) {
|
|
302
|
+
baseColumns.push({
|
|
237
303
|
field: 'successful_login',
|
|
238
304
|
headerName: t('UserList.SUCCESSFUL_LOGIN', 'Successful Login'),
|
|
239
|
-
minWidth:
|
|
240
|
-
|
|
305
|
+
minWidth: 120,
|
|
306
|
+
maxWidth: 150,
|
|
307
|
+
flex: 0.4,
|
|
308
|
+
align: 'center',
|
|
309
|
+
headerAlign: 'center',
|
|
241
310
|
valueGetter: (_value, row) => Boolean(row?.successful_login ?? row?.last_login),
|
|
242
|
-
renderCell: (params) => (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
: t('Common.NO', 'No')}
|
|
247
|
-
</Typography>
|
|
311
|
+
renderCell: (params) => renderBooleanStatusIcon(
|
|
312
|
+
Boolean(params.row?.successful_login ?? params.row?.last_login),
|
|
313
|
+
t('Common.YES', 'Yes'),
|
|
314
|
+
t('Common.NO', 'No'),
|
|
248
315
|
),
|
|
249
|
-
}
|
|
250
|
-
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (showRoleColumn) {
|
|
320
|
+
baseColumns.push({
|
|
251
321
|
field: 'role',
|
|
252
322
|
headerName: t('UserList.ROLE', 'Role'),
|
|
253
323
|
minWidth: 180,
|
|
254
|
-
|
|
324
|
+
maxWidth: 240,
|
|
325
|
+
flex: 0.7,
|
|
255
326
|
valueGetter: (_value, row) => row?.role || 'none',
|
|
256
327
|
renderCell: (params) => {
|
|
257
328
|
const user = params.row;
|
|
@@ -267,13 +338,14 @@ export function UserListComponent({
|
|
|
267
338
|
</FormControl>
|
|
268
339
|
);
|
|
269
340
|
},
|
|
270
|
-
}
|
|
271
|
-
|
|
341
|
+
});
|
|
342
|
+
}
|
|
272
343
|
|
|
273
344
|
const mappedExtraColumns = visibleExtraColumns.map((column) => ({
|
|
274
345
|
field: `extra:${column.key}`,
|
|
275
346
|
headerName: typeof column.label === 'function' ? column.label(listContext) : column.label,
|
|
276
347
|
minWidth: Number(column.minWidth) || 180,
|
|
348
|
+
maxWidth: Number(column.maxWidth) || 320,
|
|
277
349
|
flex: Number(column.flex) || 0.9,
|
|
278
350
|
sortable: column.sortable !== false,
|
|
279
351
|
align: column.align || 'left',
|
|
@@ -290,11 +362,18 @@ export function UserListComponent({
|
|
|
290
362
|
}),
|
|
291
363
|
}));
|
|
292
364
|
|
|
365
|
+
const hasActionColumn = showDeleteAction || visibleRowActions.length > 0;
|
|
366
|
+
|
|
367
|
+
if (!hasActionColumn) {
|
|
368
|
+
return [...baseColumns, ...mappedExtraColumns];
|
|
369
|
+
}
|
|
370
|
+
|
|
293
371
|
const actionColumn = {
|
|
294
372
|
field: 'actions',
|
|
295
373
|
headerName: t('Common.ACTIONS', 'Actions'),
|
|
296
|
-
minWidth: Math.max(220, 110 + visibleRowActions.length * 110),
|
|
297
|
-
|
|
374
|
+
minWidth: Math.max(220, 110 + (visibleRowActions.length + (showDeleteAction ? 1 : 0)) * 110),
|
|
375
|
+
maxWidth: Math.max(360, 120 + (visibleRowActions.length + (showDeleteAction ? 1 : 0)) * 120),
|
|
376
|
+
flex: 1.1,
|
|
298
377
|
sortable: false,
|
|
299
378
|
filterable: false,
|
|
300
379
|
disableColumnMenu: true,
|
|
@@ -332,21 +411,23 @@ export function UserListComponent({
|
|
|
332
411
|
);
|
|
333
412
|
})}
|
|
334
413
|
|
|
335
|
-
|
|
336
|
-
<
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
414
|
+
{showDeleteAction && (
|
|
415
|
+
<Tooltip title={t('Common.DELETE', 'Delete')}>
|
|
416
|
+
<span>
|
|
417
|
+
<Button
|
|
418
|
+
size="small"
|
|
419
|
+
variant="outlined"
|
|
420
|
+
color="error"
|
|
421
|
+
startIcon={<DeleteIcon />}
|
|
422
|
+
onClick={() => handleDelete(user.id)}
|
|
423
|
+
disabled={!canDelete(user)}
|
|
424
|
+
sx={actionButtonSx}
|
|
425
|
+
>
|
|
426
|
+
{t('Common.DELETE', 'Delete')}
|
|
427
|
+
</Button>
|
|
428
|
+
</span>
|
|
429
|
+
</Tooltip>
|
|
430
|
+
)}
|
|
350
431
|
</Box>
|
|
351
432
|
);
|
|
352
433
|
},
|
|
@@ -356,6 +437,10 @@ export function UserListComponent({
|
|
|
356
437
|
}, [
|
|
357
438
|
t,
|
|
358
439
|
roles,
|
|
440
|
+
showNewColumn,
|
|
441
|
+
showSuccessfulLoginColumn,
|
|
442
|
+
showRoleColumn,
|
|
443
|
+
showDeleteAction,
|
|
359
444
|
visibleExtraColumns,
|
|
360
445
|
visibleRowActions,
|
|
361
446
|
listContext,
|
|
@@ -364,6 +449,7 @@ export function UserListComponent({
|
|
|
364
449
|
extraContext,
|
|
365
450
|
loadUsers,
|
|
366
451
|
canEdit,
|
|
452
|
+
canDelete,
|
|
367
453
|
]);
|
|
368
454
|
|
|
369
455
|
return (
|
|
@@ -38,6 +38,13 @@ export function AccountPage({
|
|
|
38
38
|
userListExtraContext = null,
|
|
39
39
|
userListRefreshTrigger = 0,
|
|
40
40
|
userListCanEditUser = null,
|
|
41
|
+
userListShowNewColumn = true,
|
|
42
|
+
userListShowSuccessfulLoginColumn = true,
|
|
43
|
+
userListShowRoleColumn = true,
|
|
44
|
+
userListOnChangeRole = null,
|
|
45
|
+
userListShowDeleteAction = true,
|
|
46
|
+
userListCanDeleteUser = null,
|
|
47
|
+
userListOnDeleteUser = null,
|
|
41
48
|
showBulkInviteCsvTab = false,
|
|
42
49
|
bulkInviteCsvProps = {},
|
|
43
50
|
extraTabs = [],
|
|
@@ -227,6 +234,13 @@ export function AccountPage({
|
|
|
227
234
|
extraContext={userListExtraContext}
|
|
228
235
|
refreshTrigger={userListRefreshTrigger}
|
|
229
236
|
canEditUser={userListCanEditUser}
|
|
237
|
+
showNewColumn={userListShowNewColumn}
|
|
238
|
+
showSuccessfulLoginColumn={userListShowSuccessfulLoginColumn}
|
|
239
|
+
showRoleColumn={userListShowRoleColumn}
|
|
240
|
+
onChangeRole={userListOnChangeRole}
|
|
241
|
+
showDeleteAction={userListShowDeleteAction}
|
|
242
|
+
canDeleteUser={userListCanDeleteUser}
|
|
243
|
+
onDeleteUser={userListOnDeleteUser}
|
|
230
244
|
/>
|
|
231
245
|
</Box>
|
|
232
246
|
)}
|