From 5547b427bb8221c2b25e2505f7f6d0f8b192c3fb Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Fri, 8 May 2026 14:24:31 +0500 Subject: [PATCH] Add nonzero balance filter to users page, link from dashboard card --- src/app/admin/dashboard/page.tsx | 4 ++-- src/app/admin/users/page.tsx | 19 ++++++++++++--- src/components/admin/users-search.tsx | 33 +++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/app/admin/dashboard/page.tsx b/src/app/admin/dashboard/page.tsx index fc13c51..3b34ef3 100644 --- a/src/app/admin/dashboard/page.tsx +++ b/src/app/admin/dashboard/page.tsx @@ -170,7 +170,7 @@ export default async function AdminDashboard() { -
+

На балансах

@@ -178,7 +178,7 @@ export default async function AdminDashboard() { {totalBalance.toLocaleString("ru-RU", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ₽

сумма по всем пользователям

-
+ diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index 9a6756f..d937151 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -8,14 +8,25 @@ import { UsersSearch } from "@/components/admin/users-search"; const PAGE_SIZE = 20; interface Props { - searchParams: Promise<{ search?: string; role?: string; page?: string }>; + searchParams: Promise<{ search?: string; role?: string; page?: string; balance?: string }>; } export default async function UsersPage({ searchParams }: Props) { - const { search = "", role = "", page = "1" } = await searchParams; + const { search = "", role = "", page = "1", balance = "" } = await searchParams; const currentPage = Math.max(1, parseInt(page) || 1); const skip = (currentPage - 1) * PAGE_SIZE; + // Collect userIds with non-zero balance if filter is active + let balanceUserIds: string[] | null = null; + if (balance === "nonzero") { + const groups = await prisma.balanceTransaction.groupBy({ + by: ["userId"], + _sum: { amount: true }, + having: { amount: { _sum: { not: { equals: 0 } } } }, + }); + balanceUserIds = groups.map((g) => g.userId); + } + const where = { ...(search ? { @@ -26,6 +37,7 @@ export default async function UsersPage({ searchParams }: Props) { } : {}), ...(role ? { role } : {}), + ...(balanceUserIds !== null ? { id: { in: balanceUserIds } } : {}), }; const [users, total] = await Promise.all([ @@ -66,6 +78,7 @@ export default async function UsersPage({ searchParams }: Props) { const params = new URLSearchParams(); if (search) params.set("search", search); if (role) params.set("role", role); + if (balance) params.set("balance", balance); params.set("page", String(p)); return `/admin/users?${params.toString()}`; } @@ -88,7 +101,7 @@ export default async function UsersPage({ searchParams }: Props) { {/* Filters */} - + diff --git a/src/components/admin/users-search.tsx b/src/components/admin/users-search.tsx index 9aa71f1..d1be4e8 100644 --- a/src/components/admin/users-search.tsx +++ b/src/components/admin/users-search.tsx @@ -13,15 +13,24 @@ const inputStyle: React.CSSProperties = { fontFamily: "inherit", }; -export function UsersSearch({ initialSearch, initialRole }: { initialSearch: string; initialRole: string }) { +export function UsersSearch({ + initialSearch, + initialRole, + initialBalance, +}: { + initialSearch: string; + initialRole: string; + initialBalance: string; +}) { const router = useRouter(); const pathname = usePathname(); const [, startTransition] = useTransition(); - function update(search: string, role: string) { + function update(search: string, role: string, balance: string) { const params = new URLSearchParams(); if (search) params.set("search", search); if (role) params.set("role", role); + if (balance) params.set("balance", balance); startTransition(() => router.push(`${pathname}?${params.toString()}`)); } @@ -36,7 +45,7 @@ export function UsersSearch({ initialSearch, initialRole }: { initialSearch: str onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")} onBlur={(e) => { e.currentTarget.style.borderColor = "var(--border)"; - update(e.currentTarget.value.trim(), initialRole); + update(e.currentTarget.value.trim(), initialRole, initialBalance); }} onKeyDown={(e) => { if (e.key === "Enter") e.currentTarget.blur(); }} /> @@ -44,7 +53,7 @@ export function UsersSearch({ initialSearch, initialRole }: { initialSearch: str - {(initialSearch || initialRole) && ( + + + {(initialSearch || initialRole || initialBalance) && (