166 lines
6.5 KiB
TypeScript
166 lines
6.5 KiB
TypeScript
'use client';
|
|
|
|
import type { MembershipRole } from '@/lib/types/MembershipRole';
|
|
import type { LeagueRosterAdminViewData } from '@/lib/view-data/LeagueRosterAdminViewData';
|
|
import { Box } from '@/ui/Box';
|
|
import { Button } from '@/ui/Button';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { Surface } from '@/ui/Surface';
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table';
|
|
import { Text } from '@/ui/Text';
|
|
import { Shield, UserMinus, UserPlus } from 'lucide-react';
|
|
import React from 'react';
|
|
|
|
interface RosterAdminTemplateProps {
|
|
viewData: LeagueRosterAdminViewData;
|
|
loading: boolean;
|
|
pendingCountLabel: string;
|
|
onApprove: (requestId: string) => Promise<void>;
|
|
onReject: (requestId: string) => Promise<void>;
|
|
onRoleChange: (driverId: string, newRole: MembershipRole) => Promise<void>;
|
|
onRemove: (driverId: string) => Promise<void>;
|
|
roleOptions: MembershipRole[];
|
|
}
|
|
|
|
export function RosterAdminTemplate({
|
|
viewData,
|
|
loading,
|
|
pendingCountLabel,
|
|
onApprove,
|
|
onReject,
|
|
onRoleChange,
|
|
onRemove,
|
|
roleOptions,
|
|
}: RosterAdminTemplateProps) {
|
|
const { joinRequests, members } = viewData;
|
|
|
|
return (
|
|
<Stack gap={8}>
|
|
{/* Join Requests Section */}
|
|
<Box>
|
|
<Stack direction="row" align="center" justify="between" mb={4}>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={UserPlus} size={4} color="primary-accent" />
|
|
<Heading level={5} color="primary-accent">PENDING JOIN REQUESTS</Heading>
|
|
</Stack>
|
|
<Box px={2} py={0.5} rounded="md" bg="rgba(25,140,255,0.1)" border borderColor="rgba(25,140,255,0.2)">
|
|
<Text size="xs" color="primary-accent" weight="bold">{pendingCountLabel}</Text>
|
|
</Box>
|
|
</Stack>
|
|
|
|
{loading ? (
|
|
<Surface variant="dark" border rounded="lg" padding={12} center>
|
|
<Text color="text-gray-500">Loading requests...</Text>
|
|
</Surface>
|
|
) : joinRequests.length > 0 ? (
|
|
<Surface variant="dark" border rounded="lg" overflow="hidden">
|
|
<Stack gap={0}>
|
|
{joinRequests.map((req) => (
|
|
<Box key={req.id} p={4} borderBottom borderColor="rgba(255,255,255,0.1)">
|
|
<Stack direction={{ base: 'col', md: 'row' }} align="center" justify="between" gap={4}>
|
|
<Stack gap={1}>
|
|
<Text weight="bold" color="white">{req.driver.name}</Text>
|
|
<Text size="xs" color="text-gray-500">{req.formattedRequestedAt}</Text>
|
|
{req.message && (
|
|
<Text size="sm" color="text-gray-400" mt={1}>"{req.message}"</Text>
|
|
)}
|
|
</Stack>
|
|
|
|
<Stack direction="row" gap={2}>
|
|
<Button onClick={() => onApprove(req.id)} variant="primary" size="sm">
|
|
Approve
|
|
</Button>
|
|
<Button onClick={() => onReject(req.id)} variant="secondary" size="sm">
|
|
Reject
|
|
</Button>
|
|
</Stack>
|
|
</Stack>
|
|
</Box>
|
|
))}
|
|
</Stack>
|
|
</Surface>
|
|
) : (
|
|
<Surface variant="dark" border rounded="lg" padding={8} center>
|
|
<Text color="text-gray-500">No pending join requests.</Text>
|
|
</Surface>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Members Section */}
|
|
<Box>
|
|
<Stack direction="row" align="center" gap={2} mb={4}>
|
|
<Icon icon={Shield} size={4} color="text-success-green" />
|
|
<Heading level={5} color="text-success-green">ACTIVE ROSTER</Heading>
|
|
</Stack>
|
|
|
|
{loading ? (
|
|
<Surface variant="dark" border rounded="lg" padding={12} center>
|
|
<Text color="text-gray-500">Loading members...</Text>
|
|
</Surface>
|
|
) : members.length > 0 ? (
|
|
<Surface variant="dark" border rounded="lg" overflow="hidden">
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableHeader>Driver</TableHeader>
|
|
<TableHeader>Joined</TableHeader>
|
|
<TableHeader>Role</TableHeader>
|
|
<TableHeader textAlign="right">Actions</TableHeader>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{members.map((member) => (
|
|
<TableRow key={member.driverId}>
|
|
<TableCell>
|
|
<Text weight="bold" color="white">{member.driver.name}</Text>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Text size="sm" color="text-gray-400">{member.formattedJoinedAt}</Text>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Box
|
|
as="select"
|
|
value={member.role}
|
|
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => onRoleChange(member.driverId, e.target.value as MembershipRole)}
|
|
bg="rgba(255,255,255,0.05)"
|
|
border
|
|
borderColor="rgba(255,255,255,0.1)"
|
|
rounded="md"
|
|
px={2}
|
|
py={1}
|
|
color="white"
|
|
>
|
|
{roleOptions.map((role) => (
|
|
<Box as="option" key={role} value={role}>{role.toUpperCase()}</Box>
|
|
))}
|
|
</Box>
|
|
</TableCell>
|
|
<TableCell textAlign="right">
|
|
<Button
|
|
onClick={() => onRemove(member.driverId)}
|
|
variant="ghost"
|
|
size="sm"
|
|
>
|
|
<Stack direction="row" align="center" gap={1.5}>
|
|
<Icon icon={UserMinus} size={3.5} color="critical-red" />
|
|
<Text size="xs" weight="bold" color="critical-red">REMOVE</Text>
|
|
</Stack>
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</Surface>
|
|
) : (
|
|
<Surface variant="dark" border rounded="lg" padding={8} center>
|
|
<Text color="text-gray-500">No members found.</Text>
|
|
</Surface>
|
|
)}
|
|
</Box>
|
|
</Stack>
|
|
);
|
|
}
|