119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
|
|
|
|
import { AlertCircle, AlertTriangle, Video } from 'lucide-react';
|
|
import { Badge } from '@/ui/Badge';
|
|
import { Box } from '@/ui/Box';
|
|
import { Button } from '@/ui/Button';
|
|
import { Card } from '@/ui/Card';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Link } from '@/ui/Link';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { Text } from '@/ui/Text';
|
|
|
|
interface ProtestListItemProps {
|
|
protesterName: string;
|
|
protesterHref: string;
|
|
accusedName: string;
|
|
accusedHref: string;
|
|
status: string;
|
|
isUrgent: boolean;
|
|
daysOld: number;
|
|
lap: number;
|
|
filedAtLabel: string;
|
|
description: string;
|
|
proofVideoUrl?: string;
|
|
decisionNotes?: string;
|
|
isAdmin: boolean;
|
|
onReview?: () => void;
|
|
}
|
|
|
|
export function ProtestListItem({
|
|
protesterName,
|
|
protesterHref,
|
|
accusedName,
|
|
accusedHref,
|
|
status,
|
|
isUrgent,
|
|
daysOld,
|
|
lap,
|
|
filedAtLabel,
|
|
description,
|
|
proofVideoUrl,
|
|
decisionNotes,
|
|
isAdmin,
|
|
onReview,
|
|
}: ProtestListItemProps) {
|
|
const getStatusVariant = (s: string): any => {
|
|
switch (s) {
|
|
case 'pending':
|
|
case 'under_review': return 'warning';
|
|
case 'upheld': return 'danger';
|
|
case 'dismissed': return 'default';
|
|
case 'withdrawn': return 'primary';
|
|
default: return 'warning';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card
|
|
borderLeft={isUrgent}
|
|
borderColor={isUrgent ? 'border-red-500' : 'border-charcoal-outline'}
|
|
style={isUrgent ? { borderLeftWidth: '4px' } : undefined}
|
|
>
|
|
<Stack direction="row" align="start" justify="between" gap={4}>
|
|
<Box flexGrow={1} minWidth="0">
|
|
<Stack direction="row" align="center" gap={2} mb={2} wrap>
|
|
<Icon icon={AlertCircle} size={4} color="rgb(156, 163, 175)" />
|
|
<Link href={protesterHref}>
|
|
<Text weight="medium" color="text-white">{protesterName}</Text>
|
|
</Link>
|
|
<Text size="sm" color="text-gray-500">vs</Text>
|
|
<Link href={accusedHref}>
|
|
<Text weight="medium" color="text-white">{accusedName}</Text>
|
|
</Link>
|
|
<Badge variant={getStatusVariant(status)}>
|
|
{status.replace('_', ' ')}
|
|
</Badge>
|
|
{isUrgent && (
|
|
<Badge variant="danger" icon={AlertTriangle}>
|
|
{daysOld}d old
|
|
</Badge>
|
|
)}
|
|
</Stack>
|
|
<Stack direction="row" align="center" gap={4} mb={2} wrap>
|
|
<Text size="sm" color="text-gray-400">Lap {lap}</Text>
|
|
<Text size="sm" color="text-gray-400">•</Text>
|
|
<Text size="sm" color="text-gray-400">Filed {filedAtLabel}</Text>
|
|
{proofVideoUrl && (
|
|
<>
|
|
<Text size="sm" color="text-gray-400">•</Text>
|
|
<Link href={proofVideoUrl} target="_blank">
|
|
<Icon icon={Video} size={3.5} mr={1.5} />
|
|
<Text size="sm">Video Evidence</Text>
|
|
</Link>
|
|
</>
|
|
)}
|
|
</Stack>
|
|
<Text size="sm" color="text-gray-300" block>{description}</Text>
|
|
|
|
{decisionNotes && (
|
|
<Box mt={4} p={3} bg="bg-charcoal-outline/30" rounded="lg" border borderColor="border-charcoal-outline/50">
|
|
<Text size="xs" color="text-gray-500" uppercase letterSpacing="0.05em" block mb={1}>Steward Decision</Text>
|
|
<Text size="sm" color="text-gray-300">{decisionNotes}</Text>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
{isAdmin && status === 'pending' && onReview && (
|
|
<Button
|
|
variant="primary"
|
|
onClick={onReview}
|
|
size="sm"
|
|
>
|
|
Review
|
|
</Button>
|
|
)}
|
|
</Stack>
|
|
</Card>
|
|
);
|
|
}
|