Add search, filters, pagination to admin users table

- Add emailVerified filter (true/false/any) to UsersSearch component
- Wire emailVerified param through page searchParams, where clause, and pageUrl helper
- Preserve emailVerified in pagination links alongside existing search/role/balance params
- Update pagination label to "Страница X из Y · Всего: N"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 14:25:53 +05:00
parent e5ba94cb33
commit 4f5b5c535a
2 changed files with 27 additions and 9 deletions
+20 -5
View File
@@ -16,20 +16,23 @@ const inputStyle: React.CSSProperties = {
export function UsersSearch({
initialSearch,
initialRole,
initialEmailVerified,
initialBalance,
}: {
initialSearch: string;
initialRole: string;
initialEmailVerified: string;
initialBalance: string;
}) {
const router = useRouter();
const pathname = usePathname();
const [, startTransition] = useTransition();
function update(search: string, role: string, balance: string) {
function update(search: string, role: string, emailVerified: string, balance: string) {
const params = new URLSearchParams();
if (search) params.set("search", search);
if (role) params.set("role", role);
if (emailVerified) params.set("emailVerified", emailVerified);
if (balance) params.set("balance", balance);
startTransition(() => router.push(`${pathname}?${params.toString()}`));
}
@@ -45,7 +48,7 @@ export function UsersSearch({
onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")}
onBlur={(e) => {
e.currentTarget.style.borderColor = "var(--border)";
update(e.currentTarget.value.trim(), initialRole, initialBalance);
update(e.currentTarget.value.trim(), initialRole, initialEmailVerified, initialBalance);
}}
onKeyDown={(e) => { if (e.key === "Enter") e.currentTarget.blur(); }}
/>
@@ -53,7 +56,7 @@ export function UsersSearch({
<select
defaultValue={initialRole}
onChange={(e) => update(initialSearch, e.target.value, initialBalance)}
onChange={(e) => update(initialSearch, e.target.value, initialEmailVerified, initialBalance)}
style={{ ...inputStyle, appearance: "none", cursor: "pointer" }}
onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")}
onBlur={(e) => (e.currentTarget.style.borderColor = "var(--border)")}
@@ -64,9 +67,21 @@ export function UsersSearch({
<option value="admin">Администраторы</option>
</select>
<select
defaultValue={initialEmailVerified}
onChange={(e) => update(initialSearch, initialRole, e.target.value, initialBalance)}
style={{ ...inputStyle, appearance: "none", cursor: "pointer" }}
onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")}
onBlur={(e) => (e.currentTarget.style.borderColor = "var(--border)")}
>
<option value="">Любой статус</option>
<option value="true">Email подтверждён</option>
<option value="false">Не подтверждён</option>
</select>
<button
type="button"
onClick={() => update(initialSearch, initialRole, initialBalance === "nonzero" ? "" : "nonzero")}
onClick={() => update(initialSearch, initialRole, initialEmailVerified, initialBalance === "nonzero" ? "" : "nonzero")}
className="text-xs px-3"
style={{
border: "2px solid var(--border)",
@@ -78,7 +93,7 @@ export function UsersSearch({
С балансом
</button>
{(initialSearch || initialRole || initialBalance) && (
{(initialSearch || initialRole || initialEmailVerified || initialBalance) && (
<button
type="button"
onClick={() => startTransition(() => router.push(pathname))}