MOPC-App/src/components/admin/jury/jury-members-table.tsx

162 lines
5.1 KiB
TypeScript
Raw Normal View History

'use client'
import { useState } from 'react'
import { Trash2, UserPlus } from 'lucide-react'
import { toast } from 'sonner'
import { trpc } from '@/lib/trpc/client'
import { Button } from '@/components/ui/button'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import { Badge } from '@/components/ui/badge'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { AddMemberDialog } from './add-member-dialog'
interface JuryMember {
id: string
userId: string
role?: string
user: {
id: string
name: string | null
email: string
}
maxAssignmentsOverride?: number | null
capModeOverride?: string | null
preferredStartupRatio?: number | null
}
interface JuryMembersTableProps {
juryGroupId: string
members: JuryMember[]
}
export function JuryMembersTable({ juryGroupId, members }: JuryMembersTableProps) {
const [addDialogOpen, setAddDialogOpen] = useState(false)
const [removingMemberId, setRemovingMemberId] = useState<string | null>(null)
const utils = trpc.useUtils()
const { mutate: removeMember, isPending: isRemoving } = trpc.juryGroup.removeMember.useMutation({
onSuccess: () => {
utils.juryGroup.getById.invalidate({ id: juryGroupId })
toast.success('Member removed successfully')
setRemovingMemberId(null)
},
onError: (err) => {
toast.error(err.message)
setRemovingMemberId(null)
},
})
const handleRemove = (memberId: string) => {
removeMember({ id: memberId })
}
return (
<div className="space-y-4">
<div className="flex justify-end">
<Button onClick={() => setAddDialogOpen(true)}>
<UserPlus className="mr-2 h-4 w-4" />
Add Member
</Button>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead className="hidden sm:table-cell">Max Assignments</TableHead>
<TableHead className="hidden lg:table-cell">Cap Mode</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{members.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className="text-center text-muted-foreground">
No members yet. Add members to get started.
</TableCell>
</TableRow>
) : (
members.map((member) => (
<TableRow key={member.id}>
<TableCell className="font-medium">
{member.user.name || 'Unnamed User'}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{member.user.email}
</TableCell>
<TableCell className="hidden sm:table-cell">
{member.maxAssignmentsOverride ?? '—'}
</TableCell>
<TableCell className="hidden lg:table-cell">
{member.capModeOverride ? (
<Badge variant="outline" className="text-[10px]">
{member.capModeOverride}
</Badge>
) : (
<span className="text-muted-foreground text-xs">Group default</span>
)}
</TableCell>
<TableCell>
<Button
variant="ghost"
size="sm"
onClick={() => setRemovingMemberId(member.id)}
disabled={isRemoving}
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
<AddMemberDialog
juryGroupId={juryGroupId}
open={addDialogOpen}
onOpenChange={setAddDialogOpen}
/>
<AlertDialog open={!!removingMemberId} onOpenChange={() => setRemovingMemberId(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Remove Member</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to remove this member from the jury group? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => removingMemberId && handleRemove(removingMemberId)}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Remove
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)
}