feat: show vote status on jury dashboard and add logos to award-master
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m47s

- Jury dashboard now shows "Submitted" badge (green) with "Edit
  Rankings" button when juror has already voted, instead of always
  showing "Vote Now" — prevents confusion about whether vote saved
- Award-master page now shows project logos next to project names
- Backend getMyAwardDetailEnhanced now returns logo URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-14 13:09:34 -04:00
parent 3a6a9a2b45
commit f1955b68f9
3 changed files with 46 additions and 14 deletions

View File

@@ -42,6 +42,7 @@ import {
import { cn } from '@/lib/utils'
import { CountryDisplay } from '@/components/shared/country-display'
import { ProjectFilesSection } from '@/components/jury/project-files-section'
import { ProjectLogo } from '@/components/shared/project-logo'
export default function AwardMasterVotingPage({
params,
@@ -254,7 +255,8 @@ export default function AwardMasterVotingPage({
onClick={() => handleProjectClick(project.id)}
>
<CardHeader className="pb-2">
<div className="flex items-start justify-between">
<div className="flex items-start gap-3">
<ProjectLogo project={project} logoUrl={project.logoUrl} size="sm" fallback="initials" className="mt-0.5" />
<div className="flex-1 min-w-0">
<CardTitle className="text-base">
{project.title}

View File

@@ -132,6 +132,17 @@ async function JuryDashboardContent() {
const juryCompareEnabled = compareFlag?.value === 'true'
// Check which awards the user has already voted on
const awardIds = myAwardJurorRecords.map((r) => r.award.id)
const myAwardVotes = awardIds.length > 0
? await prisma.awardVote.groupBy({
by: ['awardId'],
where: { userId, awardId: { in: awardIds } },
_count: { id: true },
})
: []
const votedAwardIds = new Set(myAwardVotes.filter((v) => v._count.id > 0).map((v) => v.awardId))
// Awards where voting is open
const activeAwards = myAwardJurorRecords.filter(
(r) => r.award.status === 'VOTING_OPEN'
@@ -314,35 +325,50 @@ async function JuryDashboardContent() {
const award = record.award
const deadline = award.votingEndAt ? new Date(award.votingEndAt) : null
const isUrgent = deadline && (deadline.getTime() - now.getTime()) < 24 * 60 * 60 * 1000
const hasVoted = votedAwardIds.has(award.id)
return (
<div
key={award.id}
className={cn(
'rounded-xl border p-4 space-y-3 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md',
isUrgent
? 'border-red-200 bg-red-50/50 dark:border-red-900 dark:bg-red-950/20'
: 'border-amber-200/60 bg-amber-50/30 dark:border-amber-800/40 dark:bg-amber-950/10'
hasVoted
? 'border-green-200/60 bg-green-50/30 dark:border-green-800/40 dark:bg-green-950/10'
: isUrgent
? 'border-red-200 bg-red-50/50 dark:border-red-900 dark:bg-red-950/20'
: 'border-amber-200/60 bg-amber-50/30 dark:border-amber-800/40 dark:bg-amber-950/10'
)}
>
<div className="flex items-start justify-between">
<div>
<h3 className="font-semibold text-amber-700 dark:text-amber-400">{award.name}</h3>
<h3 className={cn('font-semibold', hasVoted ? 'text-green-700 dark:text-green-400' : 'text-amber-700 dark:text-amber-400')}>{award.name}</h3>
<p className="text-xs text-muted-foreground mt-0.5">
{award._count.eligibilities} project{award._count.eligibilities !== 1 ? 's' : ''} to review
{record.isChair && ' · You are the Chair'}
</p>
</div>
<Badge className="bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-700">
Vote Now
</Badge>
{hasVoted ? (
<Badge className="bg-green-100 text-green-800 border-green-300 dark:bg-green-950 dark:text-green-300 dark:border-green-700">
<CheckCircle2 className="mr-1 h-3 w-3" />
Submitted
</Badge>
) : (
<Badge className="bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-700">
Vote Now
</Badge>
)}
</div>
{deadline && (
<CountdownTimer deadline={deadline} label="Voting closes:" />
)}
<Button asChild size="sm" className="w-full bg-amber-600 hover:bg-amber-700 text-white shadow-sm">
<Button asChild size="sm" className={cn(
'w-full shadow-sm',
hasVoted
? 'bg-green-600 hover:bg-green-700 text-white'
: 'bg-amber-600 hover:bg-amber-700 text-white'
)}>
<Link href={`/jury/awards/${award.id}`}>
Review & Vote
{hasVoted ? 'Edit Rankings' : 'Review & Vote'}
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>

View File

@@ -818,6 +818,7 @@ export const specialAwardRouter = router({
select: {
id: true, title: true, teamName: true, description: true,
competitionCategory: true, country: true, tags: true,
logoKey: true, logoProvider: true,
},
},
},
@@ -886,12 +887,15 @@ export const specialAwardRouter = router({
}))
}
const projectsWithScores = eligibleProjects.map((e) => ({
...e.project,
evaluationScore: projectScores[e.project.id] ?? null,
}))
const projectsWithLogos = await attachProjectLogoUrls(projectsWithScores)
return {
award,
projects: eligibleProjects.map((e) => ({
...e.project,
evaluationScore: projectScores[e.project.id] ?? null,
})),
projects: projectsWithLogos,
myVotes,
isChair,
otherVotes,