Files
MOPC-Portal/src/components/admin/assignment/assignment-preview-sheet.tsx

182 lines
6.2 KiB
TypeScript
Raw Normal View History

'use client'
import { useState, useEffect } from 'react'
import { AlertTriangle, Bot, CheckCircle2 } from 'lucide-react'
import { toast } from 'sonner'
import { trpc } from '@/lib/trpc/client'
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet'
import { Button } from '@/components/ui/button'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton'
interface AssignmentPreviewSheetProps {
roundId: string
open: boolean
onOpenChange: (open: boolean) => void
}
export function AssignmentPreviewSheet({
roundId,
open,
onOpenChange,
}: AssignmentPreviewSheetProps) {
const utils = trpc.useUtils()
const {
data: preview,
isLoading,
refetch,
} = trpc.roundAssignment.preview.useQuery(
{ roundId, honorIntents: true, requiredReviews: 3 },
{ enabled: open }
)
const { mutate: execute, isPending: isExecuting } = trpc.roundAssignment.execute.useMutation({
onSuccess: (result) => {
toast.success(`Created ${result.created} assignments`)
utils.roundAssignment.coverageReport.invalidate({ roundId })
utils.roundAssignment.unassignedQueue.invalidate({ roundId })
onOpenChange(false)
},
onError: (err) => {
toast.error(err.message)
},
})
useEffect(() => {
if (open) {
refetch()
}
}, [open, refetch])
const handleExecute = () => {
if (!preview?.assignments || preview.assignments.length === 0) {
toast.error('No assignments to execute')
return
}
execute({
roundId,
assignments: preview.assignments.map((a: any) => ({
userId: a.userId,
projectId: a.projectId,
})),
})
}
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="w-full sm:max-w-xl">
<SheetHeader>
<SheetTitle>Assignment Preview</SheetTitle>
<SheetDescription className="flex items-center gap-2">
<Badge variant="outline" className="text-xs gap-1 shrink-0">
<Bot className="h-3 w-3" />
AI Suggested
</Badge>
Review the proposed assignments before executing. All assignments are admin-approved on execute.
</SheetDescription>
</SheetHeader>
<ScrollArea className="h-[calc(100vh-200px)] mt-6">
{isLoading ? (
<div className="space-y-3">
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-20 w-full" />
))}
</div>
) : preview ? (
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-sm flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-green-600" />
{preview.stats.assignmentsGenerated || 0} Assignments Proposed
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{preview.stats.totalJurors || 0} jurors will receive assignments
</p>
</CardContent>
</Card>
{preview.warnings && preview.warnings.length > 0 && (
<Card className="border-amber-500">
<CardHeader>
<CardTitle className="text-sm flex items-center gap-2">
<AlertTriangle className="h-4 w-4 text-amber-600" />
Warnings ({preview.warnings.length})
</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm">
{preview.warnings.map((warning: string, idx: number) => (
<li key={idx} className="flex items-start gap-2">
<span className="text-amber-600"></span>
<span>{warning}</span>
</li>
))}
</ul>
</CardContent>
</Card>
)}
{preview.assignments && preview.assignments.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="text-sm">Assignment Summary</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Total assignments:</span>
<span className="font-medium">{preview.assignments.length}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Unique projects:</span>
<span className="font-medium">
{new Set(preview.assignments.map((a: any) => a.projectId)).size}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Unique jurors:</span>
<span className="font-medium">
{new Set(preview.assignments.map((a: any) => a.userId)).size}
</span>
</div>
</div>
</CardContent>
</Card>
)}
</div>
) : (
<p className="text-sm text-muted-foreground">No preview data available</p>
)}
</ScrollArea>
<SheetFooter className="mt-6">
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button
onClick={handleExecute}
disabled={isExecuting || !preview?.assignments || preview.assignments.length === 0}
>
{isExecuting ? 'Executing...' : 'Execute Assignments'}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
)
}