Add observer project detail page with files, evaluations & reviews
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m59s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m59s
New page at /observer/projects/[projectId] showing project info, documents grouped by round requirements, and jury evaluations with click-through to full review details. Dashboard table rows now link to project detail. Also cleans up redundant programName prefixes and fixes chart edge cases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import type { Route } from 'next'
|
||||
import { trpc } from '@/lib/trpc/client'
|
||||
import {
|
||||
Card,
|
||||
@@ -150,26 +151,6 @@ export function ObserverDashboardContent({ userName }: { userName?: string }) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Observer Notice */}
|
||||
<div className="rounded-lg border border-blue-200 bg-blue-50/50 px-4 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-blue-100 p-2.5">
|
||||
<Eye className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="font-semibold text-blue-900">Observer Mode</p>
|
||||
<Badge variant="outline" className="border-blue-300 text-blue-700 text-xs">
|
||||
Read-Only
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-blue-700">
|
||||
You have read-only access to view platform statistics and reports.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Round Filter */}
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4">
|
||||
<label className="text-sm font-medium">Filter by Round:</label>
|
||||
@@ -181,7 +162,7 @@ export function ObserverDashboardContent({ userName }: { userName?: string }) {
|
||||
<SelectItem value="all">All Rounds</SelectItem>
|
||||
{rounds.map((round) => (
|
||||
<SelectItem key={round.id} value={round.id}>
|
||||
{round.programName} - {round.name}{round.roundType ? ` (${round.roundType.replace(/_/g, ' ')})` : ''}
|
||||
{round.name}{round.roundType ? ` (${round.roundType.replace(/_/g, ' ')})` : ''}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -364,7 +345,6 @@ export function ObserverDashboardContent({ userName }: { userName?: string }) {
|
||||
Title<SortIcon column="title" />
|
||||
</button>
|
||||
</TableHead>
|
||||
<TableHead>Team</TableHead>
|
||||
<TableHead>Round</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">
|
||||
@@ -381,11 +361,12 @@ export function ObserverDashboardContent({ userName }: { userName?: string }) {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{projectsData.projects.map((project) => (
|
||||
<TableRow key={project.id}>
|
||||
<TableCell className="font-medium max-w-[250px] truncate">
|
||||
{project.title}
|
||||
<TableRow key={project.id} className="cursor-pointer hover:bg-muted/50" onClick={() => window.location.href = `/observer/projects/${project.id}`}>
|
||||
<TableCell className="font-medium max-w-[300px] truncate">
|
||||
<Link href={`/observer/projects/${project.id}` as Route} className="hover:underline" onClick={(e) => e.stopPropagation()}>
|
||||
{project.title}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[150px] truncate">{project.teamName || '-'}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="text-xs whitespace-nowrap">
|
||||
{project.roundName}
|
||||
@@ -411,26 +392,25 @@ export function ObserverDashboardContent({ userName }: { userName?: string }) {
|
||||
{/* Mobile Cards */}
|
||||
<div className="space-y-3 md:hidden">
|
||||
{projectsData.projects.map((project) => (
|
||||
<Card key={project.id}>
|
||||
<CardContent className="pt-4 space-y-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="font-medium text-sm leading-tight">{project.title}</p>
|
||||
<StatusBadge status={project.status} />
|
||||
</div>
|
||||
{project.teamName && (
|
||||
<p className="text-xs text-muted-foreground">{project.teamName}</p>
|
||||
)}
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{project.roundName}
|
||||
</Badge>
|
||||
<div className="flex gap-3">
|
||||
<span>Score: {project.averageScore !== null ? project.averageScore.toFixed(2) : '-'}</span>
|
||||
<span>{project.evaluationCount} eval{project.evaluationCount !== 1 ? 's' : ''}</span>
|
||||
<Link key={project.id} href={`/observer/projects/${project.id}` as Route}>
|
||||
<Card className="transition-colors hover:bg-muted/50">
|
||||
<CardContent className="pt-4 space-y-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="font-medium text-sm leading-tight">{project.title}</p>
|
||||
<StatusBadge status={project.status} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{project.roundName}
|
||||
</Badge>
|
||||
<div className="flex gap-3">
|
||||
<span>Score: {project.averageScore !== null ? project.averageScore.toFixed(2) : '-'}</span>
|
||||
<span>{project.evaluationCount} eval{project.evaluationCount !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user