Files
J-Board-Lite/src/app/(admin)/admin/users/_components/users-table.tsx
2026-04-30 21:48:59 +10:00

144 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { batchUpdateUserStatus } from "@/actions/admin/users";
import { BatchActionBar, BatchActionButton } from "@/components/admin/batch-action-bar";
import { DataTableShell } from "@/components/admin/data-table-shell";
import {
DataTable,
DataTableBody,
DataTableCell,
DataTableHead,
DataTableHeadCell,
DataTableHeaderRow,
DataTableRow,
} from "@/components/shared/data-table";
import { UserRoleBadge, UserStatusBadge } from "@/components/shared/domain-badges";
import { formatDateShort } from "@/lib/utils";
import { UserActions } from "../user-actions";
import type { AdminUserRow } from "../users-data";
interface UsersTableProps {
users: AdminUserRow[];
}
export function UsersTable({ users }: UsersTableProps) {
return (
<DataTableShell
isEmpty={users.length === 0}
emptyTitle="暂无用户"
emptyDescription="创建或注册后用户会显示在这里。"
toolbar={
<BatchActionBar
id="user-batch-form"
action={batchUpdateUserStatus}
className="rounded-none bg-transparent"
>
<BatchActionButton name="status" value="ACTIVE">
</BatchActionButton>
<BatchActionButton name="status" value="DISABLED">
</BatchActionButton>
<BatchActionButton name="status" value="BANNED" destructive>
</BatchActionButton>
</BatchActionBar>
}
mobileCards={users.map((user) => (
<article key={user.id} className="space-y-3 p-4">
<div className="flex items-start gap-3">
<input
form="user-batch-form"
type="checkbox"
name="userIds"
value={user.id}
aria-label={`选择用户 ${user.email}`}
className="mt-1 size-4 rounded border-border accent-primary"
/>
<div className="min-w-0 flex-1">
<p className="break-all text-sm font-semibold">{user.email}</p>
<p className="mt-1 break-words text-xs text-muted-foreground">{user.name || "未设置昵称"}</p>
</div>
<UserStatusBadge status={user.status} />
</div>
<div className="grid grid-cols-2 gap-2 rounded-lg bg-muted/25 p-3 text-xs">
<div>
<p className="text-muted-foreground"></p>
<div className="mt-1"><UserRoleBadge role={user.role} /></div>
</div>
<div>
<p className="text-muted-foreground"></p>
<p className="mt-1 font-semibold tabular-nums">{user._count.subscriptions}</p>
</div>
<div>
<p className="text-muted-foreground"></p>
<p className="mt-1">{user._count.invitedUsers} </p>
</div>
<div>
<p className="text-muted-foreground"></p>
<p className="mt-1">{formatDateShort(user.createdAt)}</p>
</div>
</div>
<div className="flex justify-end">
<UserActions user={user} />
</div>
</article>
))}
>
<DataTable aria-label="用户列表" className="min-w-[980px]">
<DataTableHead>
<DataTableHeaderRow>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell></DataTableHeadCell>
<DataTableHeadCell className="text-right"></DataTableHeadCell>
</DataTableHeaderRow>
</DataTableHead>
<DataTableBody>
{users.map((user) => (
<DataTableRow key={user.id}>
<DataTableCell>
<input
form="user-batch-form"
type="checkbox"
name="userIds"
value={user.id}
aria-label={`选择用户 ${user.email}`}
/>
</DataTableCell>
<DataTableCell className="max-w-56 whitespace-normal break-all font-medium">{user.email}</DataTableCell>
<DataTableCell className="max-w-44 whitespace-normal break-words text-muted-foreground">{user.name || "—"}</DataTableCell>
<DataTableCell className="max-w-40 whitespace-normal break-all text-muted-foreground">{user.inviteCode || "—"}</DataTableCell>
<DataTableCell>
<UserRoleBadge role={user.role} />
</DataTableCell>
<DataTableCell>
<UserStatusBadge status={user.status} />
</DataTableCell>
<DataTableCell className="tabular-nums">{user._count.subscriptions}</DataTableCell>
<DataTableCell className="text-xs text-muted-foreground">
<div className="space-y-1">
<p> {user._count.invitedUsers} </p>
<p>{user.invitedBy?.email ?? "—"}</p>
</div>
</DataTableCell>
<DataTableCell className="whitespace-nowrap text-muted-foreground">
{formatDateShort(user.createdAt)}
</DataTableCell>
<DataTableCell>
<div className="flex justify-end">
<UserActions user={user} />
</div>
</DataTableCell>
</DataTableRow>
))}
</DataTableBody>
</DataTable>
</DataTableShell>
);
}