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:
parent
db728830d4
commit
c063f5bba3
|
|
@ -0,0 +1,15 @@
|
|||
Notes: Filtering Round: Criteria- Older than 3 years (for all in the startup category), those who submit something random (like a spam project) (AI?)
|
||||
|
||||
-Add filters to the page (who sent documents, etc.)
|
||||
|
||||
-Partners section should be a semi-crm system to track possible sponsors and partners
|
||||
|
||||
-No translation into french (no localization)
|
||||
|
||||
-Ameliorate the user experience (make it more simple)
|
||||
|
||||
-Special Awards- Specific Jury Members (jury members will have to choose amongst projects that fit specific criteria (like the country they're based))- Spotlight on Africa, Coup de Coeur - Make a special award section and make a special case for judge invitation for special awards and allow us to make special awards and assign them to the selection of judges for the special awards specifically (And give them their own login space to see everything)
|
||||
-Invite jury member (with tag for special awards) --> Make special award (Criteria needed, Add a tag for special award (so for example, if a location is italy it will auto have the tag for COup de Coeur (since it's criteria is it only exists in certain countries))) - This is also a separate Jury Round - Use AI to sort through elligible projects based on the plain-language criteria (so the AI interprets the criteria and all projects and smart-assigns them to the round) - Make sure this round allows them to simply choose which project will win (since they have independent criteria) - Make a mix of voting for which project wins the project (or recommended project for the award), but also in the round they can simply assign a project to the award without any criteria requirements and such
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.9 MiB |
|
|
@ -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>
|
||||
{!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" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue