refactor(admin): embed roles data in user list query and update role display

- remove separate `/zen/api/roles` fetch and `roleColorMap` state from UsersPage
- update SQL query to include aggregated roles array per user via subquery
- replace single role badge with multi-badge display supporting overflow indicator
This commit is contained in:
2026-04-24 15:20:51 -04:00
parent d6b7575444
commit 70000e0761
2 changed files with 26 additions and 21 deletions
+16 -19
View File
@@ -12,7 +12,6 @@ const UsersPageClient = ({ currentUserId }) => {
const toast = useToast();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [roleColorMap, setRoleColorMap] = useState({});
const [editingUserId, setEditingUserId] = useState(null);
const [pagination, setPagination] = useState({
@@ -48,12 +47,23 @@ const UsersPageClient = ({ currentUserId }) => {
key: 'role',
label: 'Rôle',
sortable: true,
render: (user) => (
<Badge color={roleColorMap[user.role?.toLowerCase()]}>
{user.role}
render: (user) => {
const roles = user.roles || [];
const visible = roles.slice(0, 3);
const overflow = roles.length - 3;
return (
<div className="flex flex-wrap gap-1">
{visible.map(role => (
<Badge key={role.id} color={role.color || undefined}>
{role.name}
</Badge>
),
skeleton: { height: 'h-6', width: '80px', className: 'rounded-full' },
))}
{overflow > 0 && <Badge>+{overflow}</Badge>}
{roles.length === 0 && <span className="text-xs text-neutral-400"></span>}
</div>
);
},
skeleton: { height: 'h-6', width: '140px', className: 'rounded-full' },
},
{
key: 'email_verified',
@@ -116,19 +126,6 @@ const UsersPageClient = ({ currentUserId }) => {
}
};
useEffect(() => {
fetch('/zen/api/roles', { credentials: 'include' })
.then(r => r.json())
.then(data => {
const map = {};
for (const role of data.roles || []) {
if (role.color) map[role.name.toLowerCase()] = role.color;
}
setRoleColorMap(map);
})
.catch(() => {});
}, []);
useEffect(() => {
fetchUsers();
}, [sortBy, sortOrder, pagination.page, pagination.limit]);
+9 -1
View File
@@ -129,7 +129,15 @@ async function handleListUsers(request) {
const quotedSortColumn = `"${sortColumn}"`;
const result = await query(
`SELECT id, email, name, role, image, email_verified, created_at FROM zen_auth_users ORDER BY ${quotedSortColumn} ${order} LIMIT $1 OFFSET $2`,
`SELECT u.id, u.email, u.name, u.role, u.image, u.email_verified, u.created_at,
COALESCE(
(SELECT json_agg(json_build_object('id', r.id, 'name', r.name, 'color', r.color) ORDER BY r.created_at ASC)
FROM zen_auth_roles r
JOIN zen_auth_user_roles ur ON ur.role_id = r.id
WHERE ur.user_id = u.id),
'[]'::json
) AS roles
FROM zen_auth_users u ORDER BY u.${quotedSortColumn} ${order} LIMIT $1 OFFSET $2`,
[limit, offset]
);