diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index d180f52..5f2c8b3 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,6 +1,7 @@ import { redirect } from 'next/navigation' import Image from 'next/image' import { auth } from '@/lib/auth' +import { prisma } from '@/lib/prisma' export default async function AuthLayout({ children, @@ -18,16 +19,25 @@ export default async function AuthLayout({ // Redirect logged-in users to their dashboard // But NOT if they still need to set their password if (session?.user && !session.user.mustSetPassword) { - const role = session.user.role - if (role === 'SUPER_ADMIN' || role === 'PROGRAM_ADMIN') { - redirect('/admin') - } else if (role === 'JURY_MEMBER') { - redirect('/jury') - } else if (role === 'OBSERVER') { - redirect('/observer') - } else if (role === 'MENTOR') { - redirect('/mentor') + // Verify user still exists in DB (handles deleted accounts with stale sessions) + const dbUser = await prisma.user.findUnique({ + where: { id: session.user.id }, + select: { id: true }, + }) + + if (dbUser) { + const role = session.user.role + if (role === 'SUPER_ADMIN' || role === 'PROGRAM_ADMIN') { + redirect('/admin') + } else if (role === 'JURY_MEMBER') { + redirect('/jury') + } else if (role === 'OBSERVER') { + redirect('/observer') + } else if (role === 'MENTOR') { + redirect('/mentor') + } } + // If user doesn't exist in DB, fall through and show auth page } return ( diff --git a/src/app/(jury)/layout.tsx b/src/app/(jury)/layout.tsx index 6a6315e..824098e 100644 --- a/src/app/(jury)/layout.tsx +++ b/src/app/(jury)/layout.tsx @@ -18,7 +18,12 @@ export default async function JuryLayout({ select: { onboardingCompletedAt: true }, }) - if (!user?.onboardingCompletedAt) { + if (!user) { + // User was deleted — session is stale, send to login + redirect('/login') + } + + if (!user.onboardingCompletedAt) { redirect('/onboarding') } diff --git a/src/app/(mentor)/layout.tsx b/src/app/(mentor)/layout.tsx index 46fecee..00df915 100644 --- a/src/app/(mentor)/layout.tsx +++ b/src/app/(mentor)/layout.tsx @@ -19,7 +19,12 @@ export default async function MentorLayout({ select: { onboardingCompletedAt: true }, }) - if (!user?.onboardingCompletedAt) { + if (!user) { + // User was deleted — session is stale, send to login + redirect('/login') + } + + if (!user.onboardingCompletedAt) { redirect('/onboarding') } } diff --git a/src/components/admin/round/award-shortlist.tsx b/src/components/admin/round/award-shortlist.tsx index 9783486..5428854 100644 --- a/src/components/admin/round/award-shortlist.tsx +++ b/src/components/admin/round/award-shortlist.tsx @@ -32,7 +32,6 @@ import { Play, Trophy, AlertTriangle, - ChevronsUpDown, } from 'lucide-react' type AwardShortlistProps = { @@ -59,7 +58,6 @@ export function AwardShortlist({ jobDone, }: AwardShortlistProps) { const [expanded, setExpanded] = useState(false) - const [expandedReasoning, setExpandedReasoning] = useState>(new Set()) const utils = trpc.useUtils() const isRunning = jobStatus === 'PENDING' || jobStatus === 'PROCESSING' @@ -130,17 +128,6 @@ export function AwardShortlist({ const allShortlisted = shortlist && shortlist.eligibilities.length > 0 && shortlist.eligibilities.every((e) => e.shortlisted) const someShortlisted = shortlistedCount > 0 && !allShortlisted - const toggleReasoning = (id: string) => { - setExpandedReasoning((prev) => { - const next = new Set(prev) - if (next.has(id)) { - next.delete(id) - } else { - next.add(id) - } - return next - }) - } const handleBulkToggle = () => { if (!shortlist) return @@ -286,7 +273,7 @@ export function AwardShortlist({ # Project Score - Reasoning + Reasoning
{ const reasoning = (e.aiReasoningJson as Record)?.reasoning as string | undefined const isTop5 = i < shortlistSize - const isReasoningExpanded = expandedReasoning.has(e.id) return ( @@ -337,18 +323,9 @@ export function AwardShortlist({ {reasoning ? ( - +

+ {reasoning} +

) : ( )} diff --git a/src/server/routers/filtering.ts b/src/server/routers/filtering.ts index fdb4ce8..41dec0d 100644 --- a/src/server/routers/filtering.ts +++ b/src/server/routers/filtering.ts @@ -855,7 +855,13 @@ export const filteringRouter = router({ const skip = (page - 1) * perPage const where: Record = { roundId } - if (outcome) where.outcome = outcome + if (outcome) { + // Filter by effective outcome (finalOutcome if overridden, otherwise original outcome) + where.OR = [ + { finalOutcome: outcome }, + { finalOutcome: null, outcome }, + ] + } const [results, total] = await Promise.all([ ctx.prisma.filteringResult.findMany({ @@ -896,15 +902,34 @@ export const filteringRouter = router({ getResultStats: protectedProcedure .input(z.object({ roundId: z.string() })) .query(async ({ ctx, input }) => { + // Use effective outcome (finalOutcome if overridden, otherwise original outcome) const [passed, filteredOut, flagged, overridden] = await Promise.all([ ctx.prisma.filteringResult.count({ - where: { roundId: input.roundId, outcome: 'PASSED' }, + where: { + roundId: input.roundId, + OR: [ + { finalOutcome: 'PASSED' }, + { finalOutcome: null, outcome: 'PASSED' }, + ], + }, }), ctx.prisma.filteringResult.count({ - where: { roundId: input.roundId, outcome: 'FILTERED_OUT' }, + where: { + roundId: input.roundId, + OR: [ + { finalOutcome: 'FILTERED_OUT' }, + { finalOutcome: null, outcome: 'FILTERED_OUT' }, + ], + }, }), ctx.prisma.filteringResult.count({ - where: { roundId: input.roundId, outcome: 'FLAGGED' }, + where: { + roundId: input.roundId, + OR: [ + { finalOutcome: 'FLAGGED' }, + { finalOutcome: null, outcome: 'FLAGGED' }, + ], + }, }), ctx.prisma.filteringResult.count({ where: { roundId: input.roundId, overriddenBy: { not: null } }, diff --git a/src/server/services/award-eligibility-job.ts b/src/server/services/award-eligibility-job.ts index ff96272..24f77b6 100644 --- a/src/server/services/award-eligibility-job.ts +++ b/src/server/services/award-eligibility-job.ts @@ -59,9 +59,15 @@ export async function processEligibilityJob( }> if (filteringRoundId) { - // Scope to projects that PASSED filtering in the specified round + // Scope to projects that effectively PASSED filtering (including admin overrides) const passedResults = await prisma.filteringResult.findMany({ - where: { roundId: filteringRoundId, outcome: 'PASSED' }, + where: { + roundId: filteringRoundId, + OR: [ + { finalOutcome: 'PASSED' }, + { finalOutcome: null, outcome: 'PASSED' }, + ], + }, select: { projectId: true }, }) const passedIds = passedResults.map((r) => r.projectId)