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) && (