feat: add reusable MemberTable AntD component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang 2026-02-20 21:18:09 -08:00
parent b61537cd0f
commit 14e8af94a9

View file

@ -0,0 +1,120 @@
import { Member } from "@/components/networking";
import { CrownOutlined, InfoCircleOutlined, UserAddOutlined, UserOutlined } from "@ant-design/icons";
import { Button, Space, Table, Tag, Tooltip, Typography } from "antd";
import type { ColumnsType } from "antd/es/table";
import React from "react";
import TableIconActionButton from "./IconActionButton/TableIconActionButtons/TableIconActionButton";
const { Text } = Typography;
export interface MemberTableProps {
members: Member[];
canEdit: boolean;
onEdit: (member: Member) => void;
onDelete: (member: Member) => void;
onAddMember?: () => void;
roleColumnTitle?: string;
roleTooltip?: string;
extraColumns?: ColumnsType<Member>;
showDeleteForMember?: (member: Member) => boolean;
}
export default function MemberTable({
members,
canEdit,
onEdit,
onDelete,
onAddMember,
roleColumnTitle = "Role",
roleTooltip,
extraColumns = [],
showDeleteForMember,
}: MemberTableProps) {
const baseColumns: ColumnsType<Member> = [
{
title: "User Email",
dataIndex: "user_email",
key: "user_email",
render: (email: string | null) => <Text>{email || "-"}</Text>,
},
{
title: "User ID",
dataIndex: "user_id",
key: "user_id",
render: (userId: string | null) =>
userId === "default_user_id" ? (
<Tag color="blue">Default Proxy Admin</Tag>
) : (
<Text>{userId || "-"}</Text>
),
},
{
title: roleTooltip ? (
<Space direction="horizontal">
{roleColumnTitle}
<Tooltip title={roleTooltip}>
<InfoCircleOutlined />
</Tooltip>
</Space>
) : (
roleColumnTitle
),
dataIndex: "role",
key: "role",
render: (role: string) => (
<Space>
{role?.toLowerCase() === "admin" || role?.toLowerCase() === "org_admin" ? (
<CrownOutlined />
) : (
<UserOutlined />
)}
<Text style={{ textTransform: "capitalize" }}>{role || "-"}</Text>
</Space>
),
},
...extraColumns,
{
title: "Actions",
key: "actions",
fixed: "right" as const,
width: 120,
render: (_: unknown, record: Member) =>
canEdit ? (
<Space>
<TableIconActionButton
variant="Edit"
tooltipText="Edit member"
dataTestId="edit-member"
onClick={() => onEdit(record)}
/>
{(!showDeleteForMember || showDeleteForMember(record)) && (
<TableIconActionButton
variant="Delete"
tooltipText="Delete member"
dataTestId="delete-member"
onClick={() => onDelete(record)}
/>
)}
</Space>
) : null,
},
];
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Table
columns={baseColumns}
dataSource={members}
rowKey={(record) => record.user_id ?? record.user_email ?? JSON.stringify(record)}
pagination={false}
size="small"
scroll={{ x: "max-content" }}
/>
{onAddMember && canEdit && (
<Button icon={<UserAddOutlined />} type="primary" onClick={onAddMember}>
Add Member
</Button>
)}
</Space>
);
}