Display filtering controls inline for FILTERING round type
For rounds with roundType=FILTERING, the filtering controls (run button, stats, finalize) are now shown directly on the round detail page instead of requiring navigation to a separate /filtering page. Rules configuration and results review still link to their dedicated pages for detailed work. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,10 @@ import {
|
||||
Plus,
|
||||
ArrowRightCircle,
|
||||
Minus,
|
||||
XCircle,
|
||||
AlertTriangle,
|
||||
ListChecks,
|
||||
ClipboardCheck,
|
||||
} from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { AssignProjectsDialog } from '@/components/admin/assign-projects-dialog'
|
||||
@@ -64,9 +68,23 @@ function RoundDetailContent({ roundId }: { roundId: string }) {
|
||||
const [advanceOpen, setAdvanceOpen] = useState(false)
|
||||
const [removeOpen, setRemoveOpen] = useState(false)
|
||||
|
||||
const { data: round, isLoading } = trpc.round.get.useQuery({ id: roundId })
|
||||
const { data: round, isLoading, refetch: refetchRound } = trpc.round.get.useQuery({ id: roundId })
|
||||
const { data: progress } = trpc.round.getProgress.useQuery({ id: roundId })
|
||||
|
||||
// Filtering queries (only fetch for FILTERING rounds)
|
||||
const roundType = (round?.settingsJson as { roundType?: string } | null)?.roundType
|
||||
const isFilteringRound = roundType === 'FILTERING'
|
||||
|
||||
const { data: filteringStats, refetch: refetchFilteringStats } =
|
||||
trpc.filtering.getResultStats.useQuery(
|
||||
{ roundId },
|
||||
{ enabled: isFilteringRound }
|
||||
)
|
||||
const { data: filteringRules } = trpc.filtering.getRules.useQuery(
|
||||
{ roundId },
|
||||
{ enabled: isFilteringRound }
|
||||
)
|
||||
|
||||
const utils = trpc.useUtils()
|
||||
const updateStatus = trpc.round.updateStatus.useMutation({
|
||||
onSuccess: () => {
|
||||
@@ -85,6 +103,40 @@ function RoundDetailContent({ roundId }: { roundId: string }) {
|
||||
},
|
||||
})
|
||||
|
||||
// Filtering mutations
|
||||
const executeRules = trpc.filtering.executeRules.useMutation()
|
||||
const finalizeResults = trpc.filtering.finalizeResults.useMutation()
|
||||
|
||||
const handleExecuteFiltering = async () => {
|
||||
try {
|
||||
const result = await executeRules.mutateAsync({ roundId })
|
||||
toast.success(
|
||||
`Filtering complete: ${result.passed} passed, ${result.filteredOut} filtered out, ${result.flagged} flagged`
|
||||
)
|
||||
refetchFilteringStats()
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : 'Failed to execute filtering'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFinalizeFiltering = async () => {
|
||||
try {
|
||||
const result = await finalizeResults.mutateAsync({ roundId })
|
||||
toast.success(
|
||||
`Finalized: ${result.passed} passed, ${result.filteredOut} filtered out`
|
||||
)
|
||||
refetchFilteringStats()
|
||||
refetchRound()
|
||||
utils.project.list.invalidate()
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : 'Failed to finalize'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <RoundDetailSkeleton />
|
||||
}
|
||||
@@ -403,6 +455,128 @@ function RoundDetailContent({ roundId }: { roundId: string }) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Filtering Section (for FILTERING rounds) */}
|
||||
{isFilteringRound && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Filter className="h-5 w-5" />
|
||||
Project Filtering
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Run automated screening rules on projects in this round
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleExecuteFiltering}
|
||||
disabled={executeRules.isPending || !filteringRules || filteringRules.length === 0}
|
||||
>
|
||||
{executeRules.isPending ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
Run Filtering
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Stats */}
|
||||
{filteringStats && filteringStats.total > 0 ? (
|
||||
<div className="grid gap-4 sm:grid-cols-4">
|
||||
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-background">
|
||||
<Filter className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold">{filteringStats.total}</p>
|
||||
<p className="text-sm text-muted-foreground">Total</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 p-3 rounded-lg bg-green-500/10">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-green-500/20">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-green-600">
|
||||
{filteringStats.passed}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Passed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 p-3 rounded-lg bg-red-500/10">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-red-500/20">
|
||||
<XCircle className="h-5 w-5 text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-red-600">
|
||||
{filteringStats.filteredOut}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Filtered Out</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 p-3 rounded-lg bg-amber-500/10">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-amber-500/20">
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-amber-600">
|
||||
{filteringStats.flagged}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Flagged</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||
<Filter className="h-12 w-12 text-muted-foreground/50" />
|
||||
<p className="mt-2 font-medium">No filtering results yet</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure rules and run filtering to screen projects
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quick links */}
|
||||
<div className="flex flex-wrap gap-3 pt-2 border-t">
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={`/admin/rounds/${round.id}/filtering/rules`}>
|
||||
<ListChecks className="mr-2 h-4 w-4" />
|
||||
Configure Rules
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{filteringRules?.length || 0}
|
||||
</Badge>
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={`/admin/rounds/${round.id}/filtering/results`}>
|
||||
<ClipboardCheck className="mr-2 h-4 w-4" />
|
||||
Review Results
|
||||
</Link>
|
||||
</Button>
|
||||
{filteringStats && filteringStats.total > 0 && (
|
||||
<Button
|
||||
onClick={handleFinalizeFiltering}
|
||||
disabled={finalizeResults.isPending}
|
||||
variant="default"
|
||||
>
|
||||
{finalizeResults.isPending ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<CheckCircle2 className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
Finalize Results
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Quick Actions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -416,12 +590,14 @@ function RoundDetailContent({ roundId }: { roundId: string }) {
|
||||
Import Projects
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={`/admin/rounds/${round.id}/filtering`}>
|
||||
<Filter className="mr-2 h-4 w-4" />
|
||||
Manage Filtering
|
||||
</Link>
|
||||
</Button>
|
||||
{!isFilteringRound && (
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={`/admin/rounds/${round.id}/filtering`}>
|
||||
<Filter className="mr-2 h-4 w-4" />
|
||||
Manage Filtering
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={`/admin/rounds/${round.id}/assignments`}>
|
||||
<Users className="mr-2 h-4 w-4" />
|
||||
|
||||
Reference in New Issue
Block a user