Files
gridpilot.gg/apps/website/components/admin/AdminUsersTable.tsx
2026-01-18 22:55:55 +01:00

149 lines
4.5 KiB
TypeScript

'use client';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { AdminUsersViewData } from '@/lib/view-data/AdminUsersViewData';
import { Button } from '@/ui/Button';
import { IconButton } from '@/ui/IconButton';
import { SimpleCheckbox } from '@/ui/SimpleCheckbox';
import { Badge } from '@/ui/Badge';
import { DriverIdentity } from '@/ui/DriverIdentity';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '@/ui/Table';
import { Text } from '@/ui/Text';
import { MoreVertical, Trash2 } from 'lucide-react';
import { UserStatusTag } from './UserStatusTag';
import React from 'react';
interface AdminUsersTableProps {
users: AdminUsersViewData['users'];
selectedUserIds: string[];
onSelectUser: (userId: string) => void;
onSelectAll: () => void;
onUpdateStatus: (userId: string, status: string) => void;
onDeleteUser: (userId: string) => void;
deletingUserId?: string | null;
}
/**
* AdminUsersTable
*
* Semantic table for managing users.
* High-density, instrument-grade UI.
*/
export function AdminUsersTable({
users,
selectedUserIds,
onSelectUser,
onSelectAll,
onUpdateStatus,
onDeleteUser,
deletingUserId
}: AdminUsersTableProps) {
const allSelected = users.length > 0 && selectedUserIds.length === users.length;
return (
<Table>
<TableHead>
<TableRow>
<TableHeader w="2.5rem">
<SimpleCheckbox
checked={allSelected}
onChange={onSelectAll}
aria-label="Select all users"
/>
</TableHeader>
<TableHeader>User</TableHeader>
<TableHeader>Roles</TableHeader>
<TableHeader>Status</TableHeader>
<TableHeader>Last Login</TableHeader>
<TableHeader textAlign="right">Actions</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => (
<TableRow key={user.id} variant={selectedUserIds.includes(user.id) ? 'highlight' : 'default'}>
<TableCell>
<SimpleCheckbox
checked={selectedUserIds.includes(user.id)}
onChange={() => onSelectUser(user.id)}
aria-label={`Select user ${user.displayName}`}
/>
</TableCell>
<TableCell>
<DriverIdentity
driver={{
id: user.id,
name: user.displayName,
avatarUrl: null
}}
meta={user.email}
size="sm"
/>
</TableCell>
<TableCell>
<div style={{ display: 'flex', gap: '0.375rem', flexWrap: 'wrap' }}>
{user.roles.map((role) => (
<Badge key={role} variant="default" size="sm">
{role}
</Badge>
))}
</div>
</TableCell>
<TableCell>
<UserStatusTag status={user.status} />
</TableCell>
<TableCell>
<Text size="sm" variant="low">
{user.lastLoginAt ? DateDisplay.formatShort(user.lastLoginAt) : 'Never'}
</Text>
</TableCell>
<TableCell>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: '0.5rem' }}>
{user.status === 'active' ? (
<Button
size="sm"
variant="secondary"
onClick={() => onUpdateStatus(user.id, 'suspended')}
>
Suspend
</Button>
) : user.status === 'suspended' ? (
<Button
size="sm"
variant="secondary"
onClick={() => onUpdateStatus(user.id, 'active')}
>
Activate
</Button>
) : null}
<IconButton
size="sm"
variant="secondary"
onClick={() => onDeleteUser(user.id)}
disabled={deletingUserId === user.id}
icon={Trash2}
title="Delete"
/>
<IconButton
size="sm"
variant="ghost"
icon={MoreVertical}
title="More"
/>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}