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:
@@ -12,7 +12,6 @@ const UsersPageClient = ({ currentUserId }) => {
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [roleColorMap, setRoleColorMap] = useState({});
|
|
||||||
const [editingUserId, setEditingUserId] = useState(null);
|
const [editingUserId, setEditingUserId] = useState(null);
|
||||||
|
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
@@ -48,12 +47,23 @@ const UsersPageClient = ({ currentUserId }) => {
|
|||||||
key: 'role',
|
key: 'role',
|
||||||
label: 'Rôle',
|
label: 'Rôle',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (user) => (
|
render: (user) => {
|
||||||
<Badge color={roleColorMap[user.role?.toLowerCase()]}>
|
const roles = user.roles || [];
|
||||||
{user.role}
|
const visible = roles.slice(0, 3);
|
||||||
</Badge>
|
const overflow = roles.length - 3;
|
||||||
),
|
return (
|
||||||
skeleton: { height: 'h-6', width: '80px', className: 'rounded-full' },
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{visible.map(role => (
|
||||||
|
<Badge key={role.id} color={role.color || undefined}>
|
||||||
|
{role.name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{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',
|
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(() => {
|
useEffect(() => {
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
}, [sortBy, sortOrder, pagination.page, pagination.limit]);
|
}, [sortBy, sortOrder, pagination.page, pagination.limit]);
|
||||||
|
|||||||
@@ -129,7 +129,15 @@ async function handleListUsers(request) {
|
|||||||
const quotedSortColumn = `"${sortColumn}"`;
|
const quotedSortColumn = `"${sortColumn}"`;
|
||||||
|
|
||||||
const result = await query(
|
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]
|
[limit, offset]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user