Compare commits

..

No commits in common. "20db3e1e3a0555150fea12a2fd384281c46aad5f" and "1b12aa8ccd6596190676ba89c0cd43a8a7b67c54" have entirely different histories.

2 changed files with 5 additions and 200 deletions

View File

@ -358,12 +358,10 @@ export default function ProjectsPage() {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{data.projects.map((project) => { {data.projects.map((project) => (
const isEliminated = project.roundProjects?.[0]?.status === 'REJECTED'
return (
<TableRow <TableRow
key={project.id} key={project.id}
className={`group relative cursor-pointer hover:bg-muted/50 ${isEliminated ? 'opacity-60 bg-destructive/5' : ''}`} className="group relative cursor-pointer hover:bg-muted/50"
> >
<TableCell> <TableCell>
<Link <Link
@ -387,14 +385,7 @@ export default function ProjectsPage() {
</TableCell> </TableCell>
<TableCell> <TableCell>
<div> <div>
<div className="flex items-center gap-2"> <p>{project.roundProjects?.[0]?.round?.name ?? '-'}</p>
<p>{project.roundProjects?.[0]?.round?.name ?? '-'}</p>
{project.roundProjects?.[0]?.status === 'REJECTED' && (
<Badge variant="destructive" className="text-xs">
Eliminated
</Badge>
)}
</div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{project.program?.name} {project.program?.name}
</p> </p>
@ -450,7 +441,7 @@ export default function ProjectsPage() {
</DropdownMenu> </DropdownMenu>
</TableCell> </TableCell>
</TableRow> </TableRow>
)})} ))}
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
@ -492,14 +483,7 @@ export default function ProjectsPage() {
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Round</span> <span className="text-muted-foreground">Round</span>
<div className="flex items-center gap-2"> <span>{project.roundProjects?.[0]?.round?.name ?? '-'}</span>
<span>{project.roundProjects?.[0]?.round?.name ?? '-'}</span>
{project.roundProjects?.[0]?.status === 'REJECTED' && (
<Badge variant="destructive" className="text-xs">
Eliminated
</Badge>
)}
</div>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Assignments</span> <span className="text-muted-foreground">Assignments</span>

View File

@ -15,7 +15,6 @@ import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { Progress } from '@/components/ui/progress' import { Progress } from '@/components/ui/progress'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
import { import {
Table, Table,
TableBody, TableBody,
@ -35,22 +34,6 @@ import {
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from '@/components/ui/alert-dialog' } from '@/components/ui/alert-dialog'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { import {
ArrowLeft, ArrowLeft,
Users, Users,
@ -63,9 +46,7 @@ import {
Plus, Plus,
Trash2, Trash2,
RefreshCw, RefreshCw,
UserPlus,
} from 'lucide-react' } from 'lucide-react'
import { toast } from 'sonner'
interface PageProps { interface PageProps {
params: Promise<{ id: string }> params: Promise<{ id: string }>
@ -73,9 +54,6 @@ interface PageProps {
function AssignmentManagementContent({ roundId }: { roundId: string }) { function AssignmentManagementContent({ roundId }: { roundId: string }) {
const [selectedSuggestions, setSelectedSuggestions] = useState<Set<string>>(new Set()) const [selectedSuggestions, setSelectedSuggestions] = useState<Set<string>>(new Set())
const [manualDialogOpen, setManualDialogOpen] = useState(false)
const [selectedJuror, setSelectedJuror] = useState<string>('')
const [selectedProject, setSelectedProject] = useState<string>('')
const { data: round, isLoading: loadingRound } = trpc.round.get.useQuery({ id: roundId }) const { data: round, isLoading: loadingRound } = trpc.round.get.useQuery({ id: roundId })
const { data: assignments, isLoading: loadingAssignments } = trpc.assignment.listByRound.useQuery({ roundId }) const { data: assignments, isLoading: loadingAssignments } = trpc.assignment.listByRound.useQuery({ roundId })
@ -85,18 +63,6 @@ function AssignmentManagementContent({ roundId }: { roundId: string }) {
{ enabled: !!round } { enabled: !!round }
) )
// Get available jurors for manual assignment
const { data: availableJurors } = trpc.user.getJuryMembers.useQuery(
{ roundId },
{ enabled: manualDialogOpen }
)
// Get projects in this round for manual assignment
const { data: roundProjects } = trpc.project.list.useQuery(
{ roundId, perPage: 500 },
{ enabled: manualDialogOpen }
)
const utils = trpc.useUtils() const utils = trpc.useUtils()
const deleteAssignment = trpc.assignment.delete.useMutation({ const deleteAssignment = trpc.assignment.delete.useMutation({
@ -115,33 +81,6 @@ function AssignmentManagementContent({ roundId }: { roundId: string }) {
}, },
}) })
const createAssignment = trpc.assignment.create.useMutation({
onSuccess: () => {
utils.assignment.listByRound.invalidate({ roundId })
utils.assignment.getStats.invalidate({ roundId })
utils.assignment.getSuggestions.invalidate({ roundId })
setManualDialogOpen(false)
setSelectedJuror('')
setSelectedProject('')
toast.success('Assignment created successfully')
},
onError: (error) => {
toast.error(error.message || 'Failed to create assignment')
},
})
const handleCreateManualAssignment = () => {
if (!selectedJuror || !selectedProject) {
toast.error('Please select both a juror and a project')
return
}
createAssignment.mutate({
userId: selectedJuror,
projectId: selectedProject,
roundId,
})
}
if (loadingRound || loadingAssignments) { if (loadingRound || loadingAssignments) {
return <AssignmentsSkeleton /> return <AssignmentsSkeleton />
} }
@ -241,124 +180,6 @@ function AssignmentManagementContent({ roundId }: { roundId: string }) {
Manage Assignments Manage Assignments
</h1> </h1>
</div> </div>
{/* Manual Assignment Button */}
<Dialog open={manualDialogOpen} onOpenChange={setManualDialogOpen}>
<DialogTrigger asChild>
<Button>
<UserPlus className="mr-2 h-4 w-4" />
Manual Assignment
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create Manual Assignment</DialogTitle>
<DialogDescription>
Assign a jury member to evaluate a specific project
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Juror Select */}
<div className="space-y-2">
<Label htmlFor="juror">Jury Member</Label>
<Select value={selectedJuror} onValueChange={setSelectedJuror}>
<SelectTrigger id="juror">
<SelectValue placeholder="Select a jury member..." />
</SelectTrigger>
<SelectContent>
{availableJurors?.map((juror) => {
const maxAllowed = juror.maxAssignments ?? 10
const isFull = juror.currentAssignments >= maxAllowed
return (
<SelectItem key={juror.id} value={juror.id} disabled={isFull}>
<div className="flex items-center justify-between gap-4">
<span className={isFull ? 'opacity-50' : ''}>{juror.name || juror.email}</span>
<span className="text-xs text-muted-foreground">
{juror.currentAssignments}/{maxAllowed} assigned
</span>
</div>
</SelectItem>
)
})}
{availableJurors?.length === 0 && (
<div className="px-2 py-4 text-sm text-muted-foreground text-center">
No jury members available
</div>
)}
</SelectContent>
</Select>
{selectedJuror && availableJurors && (
<p className="text-xs text-muted-foreground">
{(() => {
const juror = availableJurors.find(j => j.id === selectedJuror)
if (!juror) return null
const available = (juror.maxAssignments ?? 10) - juror.currentAssignments
return `${available} assignment slot${available !== 1 ? 's' : ''} available`
})()}
</p>
)}
</div>
{/* Project Select */}
<div className="space-y-2">
<Label htmlFor="project">Project</Label>
<Select value={selectedProject} onValueChange={setSelectedProject}>
<SelectTrigger id="project">
<SelectValue placeholder="Select a project..." />
</SelectTrigger>
<SelectContent>
{roundProjects?.projects.map((project) => {
const assignmentCount = assignments?.filter(a => a.projectId === project.id).length ?? 0
const isAlreadyAssigned = selectedJuror && assignments?.some(
a => a.userId === selectedJuror && a.projectId === project.id
)
return (
<SelectItem
key={project.id}
value={project.id}
disabled={!!isAlreadyAssigned}
>
<div className="flex items-center justify-between gap-4">
<span className={isAlreadyAssigned ? 'line-through opacity-50' : ''}>
{project.title}
</span>
<span className="text-xs text-muted-foreground">
{assignmentCount}/{round.requiredReviews} reviewers
</span>
</div>
</SelectItem>
)
})}
{roundProjects?.projects.length === 0 && (
<div className="px-2 py-4 text-sm text-muted-foreground text-center">
No projects in this round
</div>
)}
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setManualDialogOpen(false)}
>
Cancel
</Button>
<Button
onClick={handleCreateManualAssignment}
disabled={!selectedJuror || !selectedProject || createAssignment.isPending}
>
{createAssignment.isPending && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Create Assignment
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div> </div>
{/* Stats */} {/* Stats */}