diff --git a/src/components/admin/assignment/individual-assignments-table.tsx b/src/components/admin/assignment/individual-assignments-table.tsx
index 4b86977..68cc24a 100644
--- a/src/components/admin/assignment/individual-assignments-table.tsx
+++ b/src/components/admin/assignment/individual-assignments-table.tsx
@@ -171,7 +171,9 @@ export function IndividualAssignmentsTable({
return items.filter((ps: any) =>
ps.project?.title?.toLowerCase().includes(q) ||
ps.project?.teamName?.toLowerCase().includes(q) ||
- ps.project?.competitionCategory?.toLowerCase().includes(q)
+ ps.project?.competitionCategory?.toLowerCase().includes(q) ||
+ (ps.project?.country || '').toLowerCase().includes(q) ||
+ (ps.project?.institution || '').toLowerCase().includes(q)
)
}, [projectStates, projectSearch])
diff --git a/src/components/admin/round/award-shortlist.tsx b/src/components/admin/round/award-shortlist.tsx
index ab573fc..d39c16a 100644
--- a/src/components/admin/round/award-shortlist.tsx
+++ b/src/components/admin/round/award-shortlist.tsx
@@ -1,9 +1,10 @@
'use client'
-import { useState } from 'react'
+import { useState, useMemo } from 'react'
import { trpc } from '@/lib/trpc/client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
import { Badge } from '@/components/ui/badge'
import { Checkbox } from '@/components/ui/checkbox'
import { Progress } from '@/components/ui/progress'
@@ -32,6 +33,7 @@ import {
Play,
Trophy,
AlertTriangle,
+ Search,
} from 'lucide-react'
type AwardShortlistProps = {
@@ -58,6 +60,7 @@ export function AwardShortlist({
jobDone,
}: AwardShortlistProps) {
const [expanded, setExpanded] = useState(false)
+ const [eligibilitySearch, setEligibilitySearch] = useState('')
const utils = trpc.useUtils()
const isRunning = jobStatus === 'PENDING' || jobStatus === 'PROCESSING'
@@ -124,6 +127,19 @@ export function AwardShortlist({
? Math.round(((currentJobDone ?? 0) / currentJobTotal) * 100)
: 0
+ const filteredEligibilities = useMemo(() => {
+ if (!shortlist) return []
+ if (!eligibilitySearch.trim()) return shortlist.eligibilities
+ const q = eligibilitySearch.toLowerCase()
+ return shortlist.eligibilities.filter((e: any) =>
+ (e.project?.title || '').toLowerCase().includes(q) ||
+ (e.project?.teamName || '').toLowerCase().includes(q) ||
+ (e.project?.country || '').toLowerCase().includes(q) ||
+ (e.project?.institution || '').toLowerCase().includes(q) ||
+ (e.project?.competitionCategory || '').toLowerCase().includes(q)
+ )
+ }, [shortlist, eligibilitySearch])
+
const shortlistedCount = shortlist?.eligibilities?.filter((e) => e.shortlisted).length ?? 0
const allShortlisted = shortlist && shortlist.eligibilities.length > 0 && shortlist.eligibilities.every((e) => e.shortlisted)
const someShortlisted = shortlistedCount > 0 && !allShortlisted
@@ -266,6 +282,18 @@ export function AwardShortlist({
)}
+
+
+
+ setEligibilitySearch(e.target.value)}
+ className="pl-9 h-9 max-w-sm"
+ />
+
+
+
@@ -288,7 +316,7 @@ export function AwardShortlist({
- {shortlist.eligibilities.map((e, i) => {
+ {filteredEligibilities.map((e, i) => {
const reasoning = (e.aiReasoningJson as Record)?.reasoning as string | undefined
const isTop5 = i < shortlistSize
return (
diff --git a/src/components/admin/round/project-states-table.tsx b/src/components/admin/round/project-states-table.tsx
index 3d6b818..d450357 100644
--- a/src/components/admin/round/project-states-table.tsx
+++ b/src/components/admin/round/project-states-table.tsx
@@ -58,6 +58,7 @@ import {
Plus,
Search,
ExternalLink,
+ Sparkles,
} from 'lucide-react'
import Link from 'next/link'
import type { Route } from 'next'
@@ -136,6 +137,14 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
onError: (err) => toast.error(err.message),
})
+ const tagProject = trpc.tag.tagProject.useMutation({
+ onSuccess: () => {
+ toast.success('AI tags generated')
+ utils.roundEngine.getProjectStates.invalidate({ roundId })
+ },
+ onError: (err: any) => toast.error(`Tag generation failed: ${err.message}`),
+ })
+
const handleTransition = (projectId: string, newState: ProjectState) => {
transitionMutation.mutate({ projectId, roundId, newState })
}
@@ -165,10 +174,17 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
}
if (searchQuery.trim()) {
const q = searchQuery.toLowerCase()
- result = result.filter((ps: any) =>
- (ps.project?.title || '').toLowerCase().includes(q) ||
- (ps.project?.teamName || '').toLowerCase().includes(q)
- )
+ result = result.filter((ps: any) => {
+ const p = ps.project
+ return (
+ (p?.title || '').toLowerCase().includes(q) ||
+ (p?.teamName || '').toLowerCase().includes(q) ||
+ (p?.country || '').toLowerCase().includes(q) ||
+ (p?.institution || '').toLowerCase().includes(q) ||
+ (p?.competitionCategory || '').toLowerCase().includes(q) ||
+ (p?.geographicZone || '').toLowerCase().includes(q)
+ )
+ })
}
return result
}, [projectStates, stateFilter, searchQuery])
@@ -411,6 +427,16 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
View Project
+ {
+ e.stopPropagation()
+ tagProject.mutate({ projectId: ps.projectId })
+ }}
+ disabled={tagProject.isPending}
+ >
+
+ Generate AI Tags
+
{PROJECT_STATES.filter((s) => s !== ps.state).map((state) => {
const sCfg = stateConfig[state]
diff --git a/src/server/routers/project-pool.ts b/src/server/routers/project-pool.ts
index 112f8f4..dd4412e 100644
--- a/src/server/routers/project-pool.ts
+++ b/src/server/routers/project-pool.ts
@@ -123,12 +123,16 @@ export const projectPoolRouter = router({
where.competitionCategory = competitionCategory
}
- // Search in title, teamName, description
+ // Search in title, teamName, description, institution, country, geographicZone, team member names
if (search) {
where.OR = [
{ title: { contains: search, mode: 'insensitive' } },
{ teamName: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
+ { institution: { contains: search, mode: 'insensitive' } },
+ { country: { contains: search, mode: 'insensitive' } },
+ { geographicZone: { contains: search, mode: 'insensitive' } },
+ { teamMembers: { some: { user: { name: { contains: search, mode: 'insensitive' } } } } },
]
}