Compare commits
No commits in common. "20db3e1e3a0555150fea12a2fd384281c46aad5f" and "1b12aa8ccd6596190676ba89c0cd43a8a7b67c54" have entirely different histories.
20db3e1e3a
...
1b12aa8ccd
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 */}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue