website refactor
This commit is contained in:
170
apps/website/components/admin/AdminUsersTable.tsx
Normal file
170
apps/website/components/admin/AdminUsersTable.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableBody,
|
||||
TableRow,
|
||||
TableHeader,
|
||||
TableCell
|
||||
} from '@/ui/Table';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { SimpleCheckbox } from '@/ui/SimpleCheckbox';
|
||||
import { UserStatusTag } from './UserStatusTag';
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { Shield, Trash2, MoreVertical } from 'lucide-react';
|
||||
import { AdminUsersViewData } from '@/lib/view-data/AdminUsersViewData';
|
||||
|
||||
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 width="10">
|
||||
<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>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Box
|
||||
bg="bg-primary-blue/10"
|
||||
rounded="full"
|
||||
p={2}
|
||||
border
|
||||
borderColor="border-primary-blue/20"
|
||||
>
|
||||
<Icon icon={Shield} size={4} color="#198CFF" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text weight="semibold" color="text-white" block>
|
||||
{user.displayName}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block>
|
||||
{user.email}
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Stack direction="row" gap={1.5} wrap>
|
||||
{user.roles.map((role) => (
|
||||
<Box
|
||||
key={role}
|
||||
px={2}
|
||||
py={0.5}
|
||||
rounded="full"
|
||||
bg="bg-charcoal-outline/30"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
>
|
||||
<Text size="xs" weight="medium" color="text-gray-300">
|
||||
{role}
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<UserStatusTag status={user.status} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text size="sm" color="text-gray-400">
|
||||
{user.lastLoginAt ? DateDisplay.formatShort(user.lastLoginAt) : 'Never'}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Stack direction="row" align="center" justify="end" gap={2}>
|
||||
{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}
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => onDeleteUser(user.id)}
|
||||
disabled={deletingUserId === user.id}
|
||||
icon={<Icon icon={Trash2} size={3} />}
|
||||
>
|
||||
{deletingUserId === user.id ? '...' : ''}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon={<Icon icon={MoreVertical} size={4} />}
|
||||
>
|
||||
{''}
|
||||
</Button>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user