From 93b3a826381a63ad91d0c11c5aaa2715791fc52b Mon Sep 17 00:00:00 2001
From: Jose Selesan
Date: Wed, 10 Jun 2026 09:44:02 -0300
Subject: [PATCH] feat(admin): implement user search functionality in admin
portal
---
.../backend/src/modules/admin/admin.routes.ts | 2 +-
.../admin/handlers/list-users.handler.ts | 3 +-
.../admin/services/admin-users.service.ts | 10 ++++-
.../src/features/admin/users-page.tsx | 41 ++++++++++++++-----
apps/frontend/src/lib/api/resources/admin.ts | 6 ++-
5 files changed, 46 insertions(+), 16 deletions(-)
diff --git a/apps/backend/src/modules/admin/admin.routes.ts b/apps/backend/src/modules/admin/admin.routes.ts
index 1de3e12..9148f1c 100644
--- a/apps/backend/src/modules/admin/admin.routes.ts
+++ b/apps/backend/src/modules/admin/admin.routes.ts
@@ -6,9 +6,9 @@ import { createPlanHandler } from '@/modules/admin/handlers/create-plan.handler'
import { deletePlanHandler } from '@/modules/admin/handlers/delete-plan.handler';
import { getUserSessionsHandler } from '@/modules/admin/handlers/get-user-sessions.handler';
import { listComplexesHandler } from '@/modules/admin/handlers/list-complexes.handler';
-import { revokeAllSessionsHandler } from '@/modules/admin/handlers/revoke-all-sessions.handler';
import { listPlansAdminHandler } from '@/modules/admin/handlers/list-plans-admin.handler';
import { listUsersHandler } from '@/modules/admin/handlers/list-users.handler';
+import { revokeAllSessionsHandler } from '@/modules/admin/handlers/revoke-all-sessions.handler';
import { unblockUserHandler } from '@/modules/admin/handlers/unblock-user.handler';
import { updatePlanHandler } from '@/modules/admin/handlers/update-plan.handler';
import type { AppEnv } from '@/types/hono';
diff --git a/apps/backend/src/modules/admin/handlers/list-users.handler.ts b/apps/backend/src/modules/admin/handlers/list-users.handler.ts
index f6a757c..deaef65 100644
--- a/apps/backend/src/modules/admin/handlers/list-users.handler.ts
+++ b/apps/backend/src/modules/admin/handlers/list-users.handler.ts
@@ -2,6 +2,7 @@ import { listAdminUsers } from '@/modules/admin/services/admin-users.service';
import type { AppContext } from '@/types/hono';
export async function listUsersHandler(c: AppContext) {
- const users = await listAdminUsers();
+ const search = c.req.query('search');
+ const users = await listAdminUsers(search);
return c.json(users);
}
diff --git a/apps/backend/src/modules/admin/services/admin-users.service.ts b/apps/backend/src/modules/admin/services/admin-users.service.ts
index 99ded34..5c01a43 100644
--- a/apps/backend/src/modules/admin/services/admin-users.service.ts
+++ b/apps/backend/src/modules/admin/services/admin-users.service.ts
@@ -13,8 +13,16 @@ type AdminUser = {
activeSessions: number;
};
-export async function listAdminUsers(): Promise {
+export async function listAdminUsers(search?: string): Promise {
const users = await db.user.findMany({
+ where: search
+ ? {
+ OR: [
+ { name: { contains: search, mode: 'insensitive' } },
+ { email: { contains: search, mode: 'insensitive' } },
+ ],
+ }
+ : undefined,
include: {
_count: {
select: { complexes: true, sessions: true },
diff --git a/apps/frontend/src/features/admin/users-page.tsx b/apps/frontend/src/features/admin/users-page.tsx
index eb4560c..7bc39f9 100644
--- a/apps/frontend/src/features/admin/users-page.tsx
+++ b/apps/frontend/src/features/admin/users-page.tsx
@@ -21,10 +21,11 @@ import {
LogIn,
Monitor,
RefreshCw,
+ Search,
ShieldAlert,
Users,
} from 'lucide-react';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
function SessionsDialog({
user,
@@ -284,10 +285,17 @@ export function UsersPage() {
const [blockingUser, setBlockingUser] = useState(null);
const [unblockingUser, setUnblockingUser] = useState(null);
const [sessionsUser, setSessionsUser] = useState(null);
+ const [searchInput, setSearchInput] = useState('');
+ const [search, setSearch] = useState('');
+
+ useEffect(() => {
+ const timer = setTimeout(() => setSearch(searchInput), 300);
+ return () => clearTimeout(timer);
+ }, [searchInput]);
const { data, isLoading } = useQuery({
- queryKey: ['admin-users'],
- queryFn: () => apiClient.admin.listUsers(),
+ queryKey: ['admin-users', search],
+ queryFn: () => apiClient.admin.listUsers(search || undefined),
});
return (
@@ -300,14 +308,25 @@ export function UsersPage() {
-
+
+
+
+ setSearchInput(e.target.value)}
+ placeholder="Buscar por nombre o email..."
+ className="h-9 w-56 rounded-xl pl-9 text-sm"
+ />
+
+
+
{blockingUser && setBlockingUser(null)} />}
diff --git a/apps/frontend/src/lib/api/resources/admin.ts b/apps/frontend/src/lib/api/resources/admin.ts
index 4d7409a..39cb380 100644
--- a/apps/frontend/src/lib/api/resources/admin.ts
+++ b/apps/frontend/src/lib/api/resources/admin.ts
@@ -51,8 +51,10 @@ export async function deletePlan(code: string) {
return response.data;
}
-export async function listUsers() {
- const response = await http.get('/api/admin/users');
+export async function listUsers(search?: string) {
+ const response = await http.get('/api/admin/users', {
+ params: search ? { search } : undefined,
+ });
return response.data;
}