Add Reviews column to Projects tab showing evaluation submission progress
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m36s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m36s
Backend: getProjectRoundStates now includes assignment counts and submitted evaluation counts per project. Frontend: new Reviews column shows X/Y (submitted/total) with green highlight when all reviews are complete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -328,7 +328,7 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
|||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="border rounded-lg overflow-hidden">
|
<div className="border rounded-lg overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="grid grid-cols-[40px_1fr_140px_160px_120px_100px_48px] gap-2 px-4 py-2.5 bg-muted/40 text-xs font-medium text-muted-foreground border-b">
|
<div className="grid grid-cols-[40px_1fr_140px_160px_120px_80px_100px_48px] gap-2 px-4 py-2.5 bg-muted/40 text-xs font-medium text-muted-foreground border-b">
|
||||||
<div>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={filtered.length > 0 && filtered.every((ps: any) => selectedIds.has(ps.projectId))}
|
checked={filtered.length > 0 && filtered.every((ps: any) => selectedIds.has(ps.projectId))}
|
||||||
@@ -339,6 +339,7 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
|||||||
<div>Category</div>
|
<div>Category</div>
|
||||||
<div>Country</div>
|
<div>Country</div>
|
||||||
<div>State</div>
|
<div>State</div>
|
||||||
|
<div>Reviews</div>
|
||||||
<div>Entered</div>
|
<div>Entered</div>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
@@ -347,10 +348,13 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
|||||||
{filtered.map((ps: any) => {
|
{filtered.map((ps: any) => {
|
||||||
const cfg = stateConfig[ps.state as ProjectState] || stateConfig.PENDING
|
const cfg = stateConfig[ps.state as ProjectState] || stateConfig.PENDING
|
||||||
const StateIcon = cfg.icon
|
const StateIcon = cfg.icon
|
||||||
|
const total = ps.totalAssignments ?? 0
|
||||||
|
const submitted = ps.submittedCount ?? 0
|
||||||
|
const allDone = total > 0 && submitted === total
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={ps.id}
|
key={ps.id}
|
||||||
className="grid grid-cols-[40px_1fr_140px_160px_120px_100px_48px] gap-2 px-4 py-3 items-center border-b last:border-b-0 hover:bg-muted/30 text-sm"
|
className="grid grid-cols-[40px_1fr_140px_160px_120px_80px_100px_48px] gap-2 px-4 py-3 items-center border-b last:border-b-0 hover:bg-muted/30 text-sm"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -381,6 +385,15 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl
|
|||||||
{cfg.label}
|
{cfg.label}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-xs tabular-nums">
|
||||||
|
{total > 0 ? (
|
||||||
|
<span className={allDone ? 'text-green-600 font-medium' : 'text-muted-foreground'}>
|
||||||
|
{submitted}/{total}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-muted-foreground">—</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
{ps.enteredAt ? new Date(ps.enteredAt).toLocaleDateString() : '—'}
|
{ps.enteredAt ? new Date(ps.enteredAt).toLocaleDateString() : '—'}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -703,7 +703,7 @@ export async function getProjectRoundStates(
|
|||||||
roundId: string,
|
roundId: string,
|
||||||
prisma: PrismaClient | any,
|
prisma: PrismaClient | any,
|
||||||
) {
|
) {
|
||||||
return prisma.projectRoundState.findMany({
|
const states = await prisma.projectRoundState.findMany({
|
||||||
where: { roundId },
|
where: { roundId },
|
||||||
include: {
|
include: {
|
||||||
project: {
|
project: {
|
||||||
@@ -714,11 +714,37 @@ export async function getProjectRoundStates(
|
|||||||
competitionCategory: true,
|
competitionCategory: true,
|
||||||
country: true,
|
country: true,
|
||||||
status: true,
|
status: true,
|
||||||
|
assignments: {
|
||||||
|
where: { roundId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
isCompleted: true,
|
||||||
|
evaluation: { select: { status: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: { enteredAt: 'desc' },
|
orderBy: { enteredAt: 'desc' },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Compute evaluation progress per project
|
||||||
|
return states.map((ps: any) => {
|
||||||
|
const assignments = ps.project?.assignments ?? []
|
||||||
|
const totalAssignments = assignments.length
|
||||||
|
const submittedCount = assignments.filter(
|
||||||
|
(a: any) => a.evaluation?.status === 'SUBMITTED'
|
||||||
|
).length
|
||||||
|
return {
|
||||||
|
...ps,
|
||||||
|
totalAssignments,
|
||||||
|
submittedCount,
|
||||||
|
project: {
|
||||||
|
...ps.project,
|
||||||
|
assignments: undefined, // strip raw assignments from response
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProjectRoundState(
|
export async function getProjectRoundState(
|
||||||
|
|||||||
Reference in New Issue
Block a user