Skip to content

Commit 7acbfe9

Browse files
authored
fix(admin): listar todos os usuários no gerenciamento (#174)
Carrega usuários administrativos em lotes paginados até completar o total retornado pela API. Isso permite gerenciar todos os usuários do banco, em vez de apenas os 50 primeiros. Também adiciona suporte a parâmetros de listagem no client do admin e cobre o fluxo com testes.
2 parents c0688fb + 5466c4d commit 7acbfe9

4 files changed

Lines changed: 94 additions & 5 deletions

File tree

front_admin/src/lib/api/users.api.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,31 @@ type AdminUserResponse = {
55
user: AdminUser;
66
};
77

8+
type AdminUsersListParams = {
9+
limit?: number;
10+
offset?: number;
11+
search?: string;
12+
role?: AdminUser["role"];
13+
isBlocked?: boolean;
14+
};
15+
16+
function buildUsersQuery(params?: AdminUsersListParams) {
17+
if (!params) return "";
18+
19+
const query = new URLSearchParams();
20+
21+
Object.entries(params).forEach(([key, value]) => {
22+
if (value === undefined || value === "") return;
23+
query.set(key, String(value));
24+
});
25+
26+
const queryString = query.toString();
27+
return queryString ? `?${queryString}` : "";
28+
}
29+
830
export const adminUsersApi = {
9-
list: () => api.get<AdminUsersListResponse>("/admin/users"),
31+
list: (params?: AdminUsersListParams) =>
32+
api.get<AdminUsersListResponse>(`/admin/users${buildUsersQuery(params)}`),
1033
block: (id: string) => api.patch<AdminUserResponse>(`/admin/users/${id}/block`),
1134
unblock: (id: string) =>
1235
api.patch<AdminUserResponse>(`/admin/users/${id}/unblock`),

front_admin/src/modules/users/UsersPage.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type RoleFilter = "all" | BackendUserRole;
2323
type StatusFilter = "all" | "active" | "blocked";
2424

2525
const USERS_PER_PAGE = 10;
26+
const USERS_FETCH_LIMIT = 100;
2627
const ROLE_VALUES: BackendUserRole[] = ["super_admin", "admin", "support", "user"];
2728

2829
const ROLE_SUMMARY: Record<
@@ -66,6 +67,28 @@ function mapUser(user: BackendAdminUser): AdminUser {
6667
};
6768
}
6869

70+
async function fetchAllUsers() {
71+
const firstPage = await adminUsersApi.list({
72+
limit: USERS_FETCH_LIMIT,
73+
offset: 0,
74+
});
75+
const allUsers = [...firstPage.data];
76+
77+
for (
78+
let offset = firstPage.offset + firstPage.limit;
79+
offset < firstPage.total;
80+
offset += firstPage.limit
81+
) {
82+
const page = await adminUsersApi.list({
83+
limit: USERS_FETCH_LIMIT,
84+
offset,
85+
});
86+
allUsers.push(...page.data);
87+
}
88+
89+
return allUsers;
90+
}
91+
6992
function StatCard({
7093
label,
7194
value,
@@ -123,11 +146,10 @@ export function UsersPage() {
123146
useEffect(() => {
124147
let active = true;
125148

126-
adminUsersApi
127-
.list()
149+
fetchAllUsers()
128150
.then((result) => {
129151
if (!active) return;
130-
setUsers(result.data.map(mapUser));
152+
setUsers(result.map(mapUser));
131153
setError(null);
132154
})
133155
.catch(() => {

front_admin/tests/lib/api/resources.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,17 @@ describe("api resources", () => {
9898

9999
it("calls admin user mutation endpoints", () => {
100100
adminUsersApi.list();
101+
adminUsersApi.list({ limit: 100, offset: 200 });
101102
adminUsersApi.block("u1");
102103
adminUsersApi.unblock("u1");
103104
adminUsersApi.changeRole("u1", "support");
104105
adminUsersApi.delete("u1");
105106

106-
expect(mockedApi.get).toHaveBeenCalledWith("/admin/users");
107+
expect(mockedApi.get).toHaveBeenNthCalledWith(1, "/admin/users");
108+
expect(mockedApi.get).toHaveBeenNthCalledWith(
109+
2,
110+
"/admin/users?limit=100&offset=200",
111+
);
107112
expect(mockedApi.patch).toHaveBeenNthCalledWith(
108113
1,
109114
"/admin/users/u1/block",

front_admin/tests/modules/users/UsersPage.test.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ const manyUsers = Array.from({ length: 12 }, (_, index) => ({
5555
isBlocked: index % 3 === 0,
5656
}));
5757

58+
const paginatedUsers = Array.from({ length: 105 }, (_, index) => ({
59+
...backendUsers[index % 2],
60+
id: `00000000-0000-4000-8000-${String(index + 100).padStart(12, "0")}`,
61+
displayName: `Paged User ${index + 1}`,
62+
username: `paged${index + 1}`,
63+
email: `paged${index + 1}@example.com`,
64+
role: index % 2 === 0 ? ("admin" as const) : ("user" as const),
65+
isBlocked: false,
66+
}));
67+
5868
describe("UsersPage", () => {
5969
beforeEach(() => {
6070
vi.clearAllMocks();
@@ -164,6 +174,35 @@ describe("UsersPage", () => {
164174
expect(screen.getByText(/Exibindo 1-/)).toBeInTheDocument();
165175
});
166176

177+
it("loads every backend page before paginating locally", async () => {
178+
vi.mocked(adminUsersApi.list).mockImplementation(({ offset = 0 } = {}) =>
179+
Promise.resolve({
180+
data: paginatedUsers.slice(offset, offset + 100),
181+
total: paginatedUsers.length,
182+
limit: 100,
183+
offset,
184+
}),
185+
);
186+
187+
renderWithProviders(<UsersPage />);
188+
189+
await screen.findByText("Paged User 1");
190+
191+
await waitFor(() =>
192+
expect(adminUsersApi.list).toHaveBeenCalledWith({
193+
limit: 100,
194+
offset: 100,
195+
}),
196+
);
197+
198+
for (let page = 1; page < 11; page += 1) {
199+
fireEvent.click(screen.getByRole("button", { name: "Próxima página" }));
200+
}
201+
202+
expect(screen.getByText("Paged User 101")).toBeInTheDocument();
203+
expect(screen.getByText("Exibindo 101-105 de 105 usuários")).toBeInTheDocument();
204+
});
205+
167206
it("unblocks a selected blocked user without changing role", async () => {
168207
renderWithProviders(<UsersPage />);
169208

0 commit comments

Comments
 (0)