ClientTable.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { Input, Table } from 'antd';
  2. import { FilterDropdownProps, SortOrder } from 'antd/lib/table/interface';
  3. import { ColumnsType } from 'antd/es/table';
  4. import { formatDistanceToNow } from 'date-fns';
  5. import { FC } from 'react';
  6. import dynamic from 'next/dynamic';
  7. import { Client } from '../../types/chat';
  8. import { UserPopover } from './UserPopover';
  9. import { BanUserButton } from './BanUserButton';
  10. import { formatUAstring } from '../../utils/format';
  11. // Lazy loaded components
  12. const SearchOutlined = dynamic(() => import('@ant-design/icons/SearchOutlined'), {
  13. ssr: false,
  14. });
  15. export type ClientTableProps = {
  16. data: Client[];
  17. };
  18. export const ClientTable: FC<ClientTableProps> = ({ data }) => {
  19. const columns: ColumnsType<Client> = [
  20. {
  21. title: 'Display Name',
  22. key: 'username',
  23. // eslint-disable-next-line react/destructuring-assignment
  24. render: (client: Client) => {
  25. const { user, connectedAt, messageCount, userAgent } = client;
  26. const connectionInfo = { connectedAt, messageCount, userAgent };
  27. return (
  28. <UserPopover user={user} connectionInfo={connectionInfo}>
  29. <span className="display-name">{user.displayName}</span>
  30. </UserPopover>
  31. );
  32. },
  33. sorter: (a: any, b: any) => b.user.displayName.localeCompare(a.user.displayName),
  34. filterIcon: <SearchOutlined />,
  35. // eslint-disable-next-line react/no-unstable-nested-components
  36. filterDropdown: ({ setSelectedKeys, selectedKeys, confirm }: FilterDropdownProps) => {
  37. if (selectedKeys.length === 0) {
  38. return null;
  39. }
  40. return (
  41. <div style={{ padding: 8 }}>
  42. <Input
  43. placeholder="Search display names..."
  44. value={selectedKeys[0].toString()} // Convert selectedKeys[0] to string
  45. onChange={e => {
  46. setSelectedKeys(e.target.value ? [e.target.value] : []);
  47. confirm({ closeDropdown: false });
  48. }}
  49. />
  50. </div>
  51. );
  52. },
  53. onFilter: (value: string, record: Client) => record.user.displayName.includes(value),
  54. sortDirections: ['descend', 'ascend'] as SortOrder[],
  55. },
  56. {
  57. title: 'Messages sent',
  58. dataIndex: 'messageCount',
  59. key: 'messageCount',
  60. className: 'number-col',
  61. width: '12%',
  62. sorter: (a: any, b: any) => a.messageCount - b.messageCount,
  63. sortDirections: ['descend', 'ascend'] as SortOrder[],
  64. render: (count: number) => <div style={{ textAlign: 'center' }}>{count}</div>,
  65. },
  66. {
  67. title: 'Connected Time',
  68. dataIndex: 'connectedAt',
  69. key: 'connectedAt',
  70. defaultSortOrder: 'ascend',
  71. render: (time: Date) => formatDistanceToNow(new Date(time)),
  72. sorter: (a: any, b: any) =>
  73. new Date(b.connectedAt).getTime() - new Date(a.connectedAt).getTime(),
  74. sortDirections: ['descend', 'ascend'] as SortOrder[],
  75. },
  76. {
  77. title: 'User Agent',
  78. dataIndex: 'userAgent',
  79. key: 'userAgent',
  80. render: (ua: string) => formatUAstring(ua),
  81. },
  82. {
  83. title: 'Location',
  84. dataIndex: 'geo',
  85. key: 'geo',
  86. render: geo => (geo ? `${geo.regionName}, ${geo.countryCode}` : '-'),
  87. },
  88. {
  89. title: '',
  90. key: 'block',
  91. className: 'actions-col',
  92. render: (_, row) => <BanUserButton user={row.user} isEnabled={!row.user.disabledAt} />,
  93. },
  94. ];
  95. return (
  96. <Table
  97. className="table-container"
  98. pagination={{ hideOnSinglePage: true }}
  99. columns={columns}
  100. dataSource={data}
  101. size="small"
  102. rowKey="id"
  103. />
  104. );
  105. };