feat: multi-role jury fix, country flags, applicant deadline banner, timeline
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

- Fix project list returning empty for users with both SUPER_ADMIN and
  JURY_MEMBER roles (jury filter now skips admins) in project, assignment,
  and evaluation routers
- Add CountryDisplay component showing flag emoji + name everywhere
  country is displayed (admin, observer, jury, mentor views — 17 files)
- Add countdown deadline banner on applicant dashboard for INTAKE,
  SUBMISSION, and MENTORING rounds with live timer
- Remove quick action buttons from applicant dashboard
- Fix competition timeline sidebar: green dots/connectors only up to
  current round, yellow dot for current round, red connector into
  rejected round, grey after

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 15:00:29 +01:00
parent a1e758bc39
commit 37351044ed
21 changed files with 172 additions and 79 deletions

View File

@@ -202,13 +202,35 @@ export function CompetitionTimelineSidebar() {
// Is this entry after the elimination point?
const isAfterElimination = eliminationIndex >= 0 && index > eliminationIndex
// Is this the current round the project is in (regardless of round status)?
const isCurrent = !!entry.projectState && entry.projectState !== 'PASSED' && entry.projectState !== 'COMPLETED' && entry.projectState !== 'REJECTED'
// Is this the current round? Either has an active project state,
// or is the first round the project hasn't passed yet (for seed data
// where project states may be missing).
const hasActiveProjectState = !!entry.projectState && entry.projectState !== 'PASSED' && entry.projectState !== 'COMPLETED' && entry.projectState !== 'REJECTED'
const isCurrent = !isAfterElimination && (hasActiveProjectState || (
!isPassed && !isRejected && !isCompleted &&
data.entries.slice(0, index).every((prev) =>
prev.projectState === 'PASSED' || prev.projectState === 'COMPLETED' ||
prev.status === 'ROUND_CLOSED' || prev.status === 'ROUND_ARCHIVED'
) && index > 0
))
// Determine connector segment color (no icons, just colored lines)
// Connector color: green up to and including the current round,
// red leading into the rejected round, neutral after.
let connectorColor = 'bg-border'
if ((isPassed || isCompleted) && !isAfterElimination) connectorColor = 'bg-emerald-400'
else if (isRejected) connectorColor = 'bg-destructive/30'
const nextEntry = data.entries[index + 1]
const nextIsRejected = nextEntry?.projectState === 'REJECTED'
if (isAfterElimination) {
connectorColor = 'bg-border'
} else if (isRejected) {
// From rejected round onward = neutral
connectorColor = 'bg-border'
} else if (nextIsRejected) {
// Connector leading INTO the rejected round = red
connectorColor = 'bg-destructive/40'
} else if (isCompleted || isPassed) {
// Rounds the project has passed through = green
connectorColor = 'bg-emerald-400'
}
// Dot inner content
let dotInner: React.ReactNode = null
@@ -222,7 +244,7 @@ export function CompetitionTimelineSidebar() {
} else if (isGrandFinale && (isCompleted || isPassed)) {
dotClasses = 'bg-yellow-500 border-2 border-yellow-500'
dotInner = <Trophy className="h-3.5 w-3.5 text-white" />
} else if (isCompleted || isPassed) {
} else if (isPassed || (isCompleted && !isCurrent)) {
dotClasses = 'bg-emerald-500 border-2 border-emerald-500'
dotInner = <Check className="h-3.5 w-3.5 text-white" />
} else if (isCurrent) {