wulf-pulse/components/admin/users/user-table.tsx

260 lines
8.6 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { Search, UserPlus, Loader2 } from "lucide-react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { RoleBadge } from "./role-badge";
import { UserActions } from "./user-actions";
import { useSession } from "@/lib/auth-client";
interface User {
id: string;
name: string;
email: string;
email_verified: boolean;
role: string;
banned: boolean;
created_at: string;
}
interface Pagination {
page: number;
limit: number;
total: number;
totalPages: number;
}
export function UserTable() {
const router = useRouter();
const searchParams = useSearchParams();
const { data: session } = useSession();
const [users, setUsers] = useState<User[]>([]);
const [pagination, setPagination] = useState<Pagination | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [search, setSearch] = useState(searchParams.get("search") || "");
const [roleFilter, setRoleFilter] = useState(searchParams.get("role") || "all");
const [statusFilter, setStatusFilter] = useState(searchParams.get("status") || "all");
async function fetchUsers() {
setIsLoading(true);
try {
const params = new URLSearchParams();
if (search) params.set("search", search);
if (roleFilter && roleFilter !== "all") params.set("role", roleFilter);
if (statusFilter && statusFilter !== "all") params.set("status", statusFilter);
params.set("page", searchParams.get("page") || "1");
const response = await fetch(`/api/admin/users?${params.toString()}`);
if (!response.ok) throw new Error("Failed to fetch users");
const data = await response.json();
setUsers(data.users);
setPagination(data.pagination);
} catch (error) {
console.error("Error fetching users:", error);
} finally {
setIsLoading(false);
}
}
useEffect(() => {
fetchUsers();
}, [searchParams]);
function handleSearch(e: React.FormEvent) {
e.preventDefault();
const params = new URLSearchParams(searchParams.toString());
if (search) {
params.set("search", search);
} else {
params.delete("search");
}
params.set("page", "1");
router.push(`/admin/users?${params.toString()}`);
}
function handleFilterChange(type: "role" | "status", value: string) {
const params = new URLSearchParams(searchParams.toString());
if (value && value !== "all") {
params.set(type, value);
} else {
params.delete(type);
}
params.set("page", "1");
if (type === "role") setRoleFilter(value);
if (type === "status") setStatusFilter(value);
router.push(`/admin/users?${params.toString()}`);
}
return (
<div className="space-y-4">
{/* Filters */}
<div className="flex flex-col sm:flex-row gap-4">
<form onSubmit={handleSearch} className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search by name or email..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-10"
/>
</div>
</form>
<div className="flex gap-2">
<Select value={roleFilter} onValueChange={(v) => handleFilterChange("role", v)}>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="Role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Roles</SelectItem>
<SelectItem value="super-admin">Super Admin</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="user">User</SelectItem>
</SelectContent>
</Select>
<Select value={statusFilter} onValueChange={(v) => handleFilterChange("status", v)}>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="banned">Banned</SelectItem>
</SelectContent>
</Select>
<Button onClick={() => router.push("/admin/users/invite")}>
<UserPlus className="mr-2 h-4 w-4" />
Invite
</Button>
</div>
</div>
{/* Table */}
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
<TableHead>Created</TableHead>
<TableHead className="w-[70px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-8">
<Loader2 className="h-6 w-6 animate-spin mx-auto" />
</TableCell>
</TableRow>
) : users.length === 0 ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-muted-foreground">
No users found
</TableCell>
</TableRow>
) : (
users.map((user) => (
<TableRow key={user.id}>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>
<div className="flex items-center gap-2">
{user.email}
{user.email_verified && (
<Badge variant="outline" className="text-xs">Verified</Badge>
)}
</div>
</TableCell>
<TableCell>
<RoleBadge role={user.role} />
</TableCell>
<TableCell>
{user.banned ? (
<Badge variant="destructive">Banned</Badge>
) : (
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200">
Active
</Badge>
)}
</TableCell>
<TableCell className="text-muted-foreground">
{new Date(user.created_at).toLocaleDateString()}
</TableCell>
<TableCell>
<UserActions
user={user}
currentUserId={session?.user?.id}
onRefresh={fetchUsers}
/>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
{/* Pagination */}
{pagination && pagination.totalPages > 1 && (
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">
Showing {(pagination.page - 1) * pagination.limit + 1} to{" "}
{Math.min(pagination.page * pagination.limit, pagination.total)} of{" "}
{pagination.total} users
</p>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
disabled={pagination.page === 1}
onClick={() => {
const params = new URLSearchParams(searchParams.toString());
params.set("page", String(pagination.page - 1));
router.push(`/admin/users?${params.toString()}`);
}}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
disabled={pagination.page === pagination.totalPages}
onClick={() => {
const params = new URLSearchParams(searchParams.toString());
params.set("page", String(pagination.page + 1));
router.push(`/admin/users?${params.toString()}`);
}}
>
Next
</Button>
</div>
</div>
)}
</div>
);
}