@micha.bigler/ui-core-micha 2.2.8 → 2.2.10

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.
@@ -19,12 +19,15 @@ const PUBLIC_PATHS = [
19
19
  "/reset", // Reset Link (/reset/:uid/:token)
20
20
  "/welcome" // Optional: Falls Welcome auch öffentlich ist
21
21
  ];
22
+ function isPublicSitePath(pathname) {
23
+ return pathname === "/sites" || pathname.startsWith("/sites/");
24
+ }
22
25
  function redirectToLoginOnce() {
23
26
  if (!isBrowser())
24
27
  return;
25
28
  const currentPath = window.location.pathname;
26
29
  // 1. Check: Sind wir auf einer öffentlichen Seite?
27
- const isPublicPage = PUBLIC_PATHS.some(path => currentPath.startsWith(path));
30
+ const isPublicPage = isPublicSitePath(currentPath) || PUBLIC_PATHS.some(path => currentPath.startsWith(path));
28
31
  // Wenn ja: NICHT weiterleiten. Der 401 Fehler wird an die Komponente durchgereicht.
29
32
  if (isPublicPage)
30
33
  return;
@@ -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, showRoleColumn = true, onChangeRole = null, showDeleteAction = true, canDeleteUser = null, onDeleteUser = null, }) {
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);
@@ -121,6 +123,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
121
123
  }
122
124
  return (user === null || user === void 0 ? void 0 : user.username) || '';
123
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" })) }) }));
124
127
  const getExtraColumnSortValue = (column, user) => {
125
128
  var _a;
126
129
  const valueContext = { user, currentUser, extraContext, t };
@@ -207,36 +210,58 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
207
210
  {
208
211
  field: 'email',
209
212
  headerName: t('Auth.EMAIL_LABEL', 'Email'),
210
- minWidth: 240,
211
- flex: 1.2,
213
+ minWidth: 220,
214
+ maxWidth: 340,
215
+ flex: 1,
212
216
  },
213
217
  {
214
218
  field: 'name',
215
219
  headerName: t('Profile.NAME_LABEL', 'Name'),
216
- minWidth: 220,
217
- flex: 1,
220
+ minWidth: 180,
221
+ maxWidth: 260,
222
+ flex: 0.9,
218
223
  valueGetter: (_value, row) => getUserDisplayName(row),
219
224
  },
220
- {
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({
221
244
  field: 'successful_login',
222
245
  headerName: t('UserList.SUCCESSFUL_LOGIN', 'Successful Login'),
223
- minWidth: 180,
224
- flex: 0.8,
246
+ minWidth: 120,
247
+ maxWidth: 150,
248
+ flex: 0.4,
249
+ align: 'center',
250
+ headerAlign: 'center',
225
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); },
226
252
  renderCell: (params) => {
227
253
  var _a, _b, _c;
228
- return (_jsx(Typography, { variant: "body2", children: 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)
229
- ? t('Common.YES', 'Yes')
230
- : 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'));
231
255
  },
232
- },
233
- ];
256
+ });
257
+ }
234
258
  if (showRoleColumn) {
235
259
  baseColumns.push({
236
260
  field: 'role',
237
261
  headerName: t('UserList.ROLE', 'Role'),
238
262
  minWidth: 180,
239
- flex: 0.8,
263
+ maxWidth: 240,
264
+ flex: 0.7,
240
265
  valueGetter: (_value, row) => (row === null || row === void 0 ? void 0 : row.role) || 'none',
241
266
  renderCell: (params) => {
242
267
  const user = params.row;
@@ -248,6 +273,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
248
273
  field: `extra:${column.key}`,
249
274
  headerName: typeof column.label === 'function' ? column.label(listContext) : column.label,
250
275
  minWidth: Number(column.minWidth) || 180,
276
+ maxWidth: Number(column.maxWidth) || 320,
251
277
  flex: Number(column.flex) || 0.9,
252
278
  sortable: column.sortable !== false,
253
279
  align: column.align || 'left',
@@ -270,7 +296,8 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
270
296
  field: 'actions',
271
297
  headerName: t('Common.ACTIONS', 'Actions'),
272
298
  minWidth: Math.max(220, 110 + (visibleRowActions.length + (showDeleteAction ? 1 : 0)) * 110),
273
- flex: 1.4,
299
+ maxWidth: Math.max(360, 120 + (visibleRowActions.length + (showDeleteAction ? 1 : 0)) * 120),
300
+ flex: 1.1,
274
301
  sortable: false,
275
302
  filterable: false,
276
303
  disableColumnMenu: true,
@@ -298,6 +325,8 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
298
325
  }, [
299
326
  t,
300
327
  roles,
328
+ showNewColumn,
329
+ showSuccessfulLoginColumn,
301
330
  showRoleColumn,
302
331
  showDeleteAction,
303
332
  visibleExtraColumns,
@@ -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, userListShowRoleColumn = true, userListOnChangeRole = null, userListShowDeleteAction = true, userListCanDeleteUser = null, userListOnDeleteUser = 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, 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 }) }))] }));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "2.2.8",
3
+ "version": "2.2.10",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -24,13 +24,17 @@ const PUBLIC_PATHS = [
24
24
  "/welcome" // Optional: Falls Welcome auch öffentlich ist
25
25
  ];
26
26
 
27
+ function isPublicSitePath(pathname) {
28
+ return pathname === "/sites" || pathname.startsWith("/sites/");
29
+ }
30
+
27
31
  function redirectToLoginOnce() {
28
32
  if (!isBrowser()) return;
29
33
 
30
34
  const currentPath = window.location.pathname;
31
35
 
32
36
  // 1. Check: Sind wir auf einer öffentlichen Seite?
33
- const isPublicPage = PUBLIC_PATHS.some(path => currentPath.startsWith(path));
37
+ const isPublicPage = isPublicSitePath(currentPath) || PUBLIC_PATHS.some(path => currentPath.startsWith(path));
34
38
 
35
39
  // Wenn ja: NICHT weiterleiten. Der 401 Fehler wird an die Komponente durchgereicht.
36
40
  if (isPublicPage) return;
@@ -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,8 @@ export function UserListComponent({
26
28
  extraContext = null,
27
29
  refreshTrigger = 0,
28
30
  canEditUser,
31
+ showNewColumn = true,
32
+ showSuccessfulLoginColumn = true,
29
33
  showRoleColumn = true,
30
34
  onChangeRole = null,
31
35
  showDeleteAction = true,
@@ -159,6 +163,18 @@ export function UserListComponent({
159
163
  return user?.username || '';
160
164
  };
161
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
+
162
178
  const getExtraColumnSortValue = (column, user) => {
163
179
  const valueContext = { user, currentUser, extraContext, t };
164
180
 
@@ -250,38 +266,63 @@ export function UserListComponent({
250
266
  {
251
267
  field: 'email',
252
268
  headerName: t('Auth.EMAIL_LABEL', 'Email'),
253
- minWidth: 240,
254
- flex: 1.2,
269
+ minWidth: 220,
270
+ maxWidth: 340,
271
+ flex: 1,
255
272
  },
256
273
  {
257
274
  field: 'name',
258
275
  headerName: t('Profile.NAME_LABEL', 'Name'),
259
- minWidth: 220,
260
- flex: 1,
276
+ minWidth: 180,
277
+ maxWidth: 260,
278
+ flex: 0.9,
261
279
  valueGetter: (_value, row) => getUserDisplayName(row),
262
280
  },
263
- {
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({
264
303
  field: 'successful_login',
265
304
  headerName: t('UserList.SUCCESSFUL_LOGIN', 'Successful Login'),
266
- minWidth: 180,
267
- flex: 0.8,
305
+ minWidth: 120,
306
+ maxWidth: 150,
307
+ flex: 0.4,
308
+ align: 'center',
309
+ headerAlign: 'center',
268
310
  valueGetter: (_value, row) => Boolean(row?.successful_login ?? row?.last_login),
269
- renderCell: (params) => (
270
- <Typography variant="body2">
271
- {Boolean(params.row?.successful_login ?? params.row?.last_login)
272
- ? t('Common.YES', 'Yes')
273
- : t('Common.NO', 'No')}
274
- </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'),
275
315
  ),
276
- },
277
- ];
316
+ });
317
+ }
278
318
 
279
319
  if (showRoleColumn) {
280
320
  baseColumns.push({
281
321
  field: 'role',
282
322
  headerName: t('UserList.ROLE', 'Role'),
283
323
  minWidth: 180,
284
- flex: 0.8,
324
+ maxWidth: 240,
325
+ flex: 0.7,
285
326
  valueGetter: (_value, row) => row?.role || 'none',
286
327
  renderCell: (params) => {
287
328
  const user = params.row;
@@ -304,6 +345,7 @@ export function UserListComponent({
304
345
  field: `extra:${column.key}`,
305
346
  headerName: typeof column.label === 'function' ? column.label(listContext) : column.label,
306
347
  minWidth: Number(column.minWidth) || 180,
348
+ maxWidth: Number(column.maxWidth) || 320,
307
349
  flex: Number(column.flex) || 0.9,
308
350
  sortable: column.sortable !== false,
309
351
  align: column.align || 'left',
@@ -330,7 +372,8 @@ export function UserListComponent({
330
372
  field: 'actions',
331
373
  headerName: t('Common.ACTIONS', 'Actions'),
332
374
  minWidth: Math.max(220, 110 + (visibleRowActions.length + (showDeleteAction ? 1 : 0)) * 110),
333
- flex: 1.4,
375
+ maxWidth: Math.max(360, 120 + (visibleRowActions.length + (showDeleteAction ? 1 : 0)) * 120),
376
+ flex: 1.1,
334
377
  sortable: false,
335
378
  filterable: false,
336
379
  disableColumnMenu: true,
@@ -394,6 +437,8 @@ export function UserListComponent({
394
437
  }, [
395
438
  t,
396
439
  roles,
440
+ showNewColumn,
441
+ showSuccessfulLoginColumn,
397
442
  showRoleColumn,
398
443
  showDeleteAction,
399
444
  visibleExtraColumns,
@@ -38,6 +38,8 @@ export function AccountPage({
38
38
  userListExtraContext = null,
39
39
  userListRefreshTrigger = 0,
40
40
  userListCanEditUser = null,
41
+ userListShowNewColumn = true,
42
+ userListShowSuccessfulLoginColumn = true,
41
43
  userListShowRoleColumn = true,
42
44
  userListOnChangeRole = null,
43
45
  userListShowDeleteAction = true,
@@ -232,6 +234,8 @@ export function AccountPage({
232
234
  extraContext={userListExtraContext}
233
235
  refreshTrigger={userListRefreshTrigger}
234
236
  canEditUser={userListCanEditUser}
237
+ showNewColumn={userListShowNewColumn}
238
+ showSuccessfulLoginColumn={userListShowSuccessfulLoginColumn}
235
239
  showRoleColumn={userListShowRoleColumn}
236
240
  onChangeRole={userListOnChangeRole}
237
241
  showDeleteAction={userListShowDeleteAction}