Polish: homework filters, users search/popup, admin comments
Homework (/curator/homework): - Search by student name/email - Filter by status (pending/reviewed) and course - Server-side pagination (20 per page) with URL params Users (/admin/users): - Search by name/email, filter by role - Hover popup on each row: enrolled courses + expiry dates + email - Pagination (20 per page) with URL params Comments (/admin/comments): - New admin page with all active comments - Search by author or text content - One-click delete (soft-delete) from the table - Pagination (30 per page) - Added "Комментарии" link to admin nav Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useTransition } from "react";
|
||||
import { Search } from "lucide-react";
|
||||
|
||||
const inputStyle: React.CSSProperties = {
|
||||
border: "2px solid var(--border)",
|
||||
background: "var(--background)",
|
||||
outline: "none",
|
||||
padding: "0.4rem 0.75rem",
|
||||
fontSize: "0.8rem",
|
||||
fontFamily: "inherit",
|
||||
};
|
||||
|
||||
export function UsersSearch({ initialSearch, initialRole }: { initialSearch: string; initialRole: string }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [, startTransition] = useTransition();
|
||||
|
||||
function update(search: string, role: string) {
|
||||
const params = new URLSearchParams();
|
||||
if (search) params.set("search", search);
|
||||
if (role) params.set("role", role);
|
||||
startTransition(() => router.push(`${pathname}?${params.toString()}`));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
<div className="relative">
|
||||
<Search size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2" style={{ color: "var(--muted-foreground)" }} />
|
||||
<input
|
||||
defaultValue={initialSearch}
|
||||
placeholder="Поиск по имени или email"
|
||||
style={{ ...inputStyle, paddingLeft: "2rem", width: 240 }}
|
||||
onFocus={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")}
|
||||
onBlur={(e) => {
|
||||
e.currentTarget.style.borderColor = "var(--border)";
|
||||
update(e.currentTarget.value.trim(), initialRole);
|
||||
}}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") e.currentTarget.blur(); }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<select
|
||||
defaultValue={initialRole}
|
||||
onChange={(e) => update(initialSearch, e.target.value)}
|
||||
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="student">Ученики</option>
|
||||
<option value="curator">Кураторы</option>
|
||||
<option value="admin">Администраторы</option>
|
||||
</select>
|
||||
|
||||
{(initialSearch || initialRole) && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => startTransition(() => router.push(pathname))}
|
||||
className="text-xs px-3"
|
||||
style={{ border: "2px solid var(--border)", color: "var(--muted-foreground)" }}
|
||||
>
|
||||
Сбросить
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user