Fix observer reports: charts, filtering, project preview, dashboard stats
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m32s
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m32s
- Rewrite diversity metrics: horizontal bar charts for ocean issues and geographic distribution (replaces unreadable vertical/donut charts) - Rewrite juror score heatmap: expandable table with score distribution - Rewrite juror consistency: horizontal bar visual with juror names - Merge filtering tabs into single screening view with per-project AI reasoning and expandable rows - Add project preview dialog for juror performance table - Fix status breakdown for evaluation rounds (Fully/Partially/Not Reviewed) - Show active round name instead of count on observer dashboard - Move Global tab to last position, default to first round-specific tab - Add 4-card stats layout for evaluation with reviews/project ratio - Fix oceanIssue field (singular) and remove non-existent aiSummary Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -126,7 +126,7 @@ export const analyticsRouter = router({
|
||||
user: { select: { name: true } },
|
||||
project: { select: { id: true, title: true } },
|
||||
evaluation: {
|
||||
select: { id: true, status: true },
|
||||
select: { id: true, status: true, globalScore: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -134,7 +134,7 @@ export const analyticsRouter = router({
|
||||
// Group by user
|
||||
const byUser: Record<
|
||||
string,
|
||||
{ name: string; assigned: number; completed: number; projects: { id: string; title: string; evalStatus: string }[] }
|
||||
{ name: string; assigned: number; completed: number; projects: { id: string; title: string; evalStatus: string; score: number | null }[] }
|
||||
> = {}
|
||||
|
||||
assignments.forEach((assignment) => {
|
||||
@@ -156,6 +156,9 @@ export const analyticsRouter = router({
|
||||
id: assignment.project.id,
|
||||
title: assignment.project.title,
|
||||
evalStatus: evalStatus === 'SUBMITTED' ? 'REVIEWED' : evalStatus === 'DRAFT' ? 'UNDER_REVIEW' : 'NOT_REVIEWED',
|
||||
score: evalStatus === 'SUBMITTED' && assignment.evaluation?.globalScore != null
|
||||
? Number(assignment.evaluation.globalScore)
|
||||
: null,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -251,7 +254,59 @@ export const analyticsRouter = router({
|
||||
.input(editionOrRoundInput)
|
||||
.query(async ({ ctx, input }) => {
|
||||
if (input.roundId) {
|
||||
// Round-level: use ProjectRoundState for accurate per-round breakdown
|
||||
// Check if this is an evaluation round — show eval-level status breakdown
|
||||
const round = await ctx.prisma.round.findUnique({
|
||||
where: { id: input.roundId },
|
||||
select: { roundType: true },
|
||||
})
|
||||
|
||||
if (round?.roundType === 'EVALUATION') {
|
||||
// For evaluation rounds, break down by evaluation status per project
|
||||
const projects = await ctx.prisma.projectRoundState.findMany({
|
||||
where: { roundId: input.roundId },
|
||||
select: {
|
||||
projectId: true,
|
||||
project: {
|
||||
select: {
|
||||
assignments: {
|
||||
where: { roundId: input.roundId },
|
||||
select: {
|
||||
evaluation: { select: { status: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
let fullyReviewed = 0
|
||||
let partiallyReviewed = 0
|
||||
let notReviewed = 0
|
||||
|
||||
for (const p of projects) {
|
||||
const assignments = p.project.assignments
|
||||
if (assignments.length === 0) {
|
||||
notReviewed++
|
||||
continue
|
||||
}
|
||||
const submitted = assignments.filter((a) => a.evaluation?.status === 'SUBMITTED').length
|
||||
if (submitted === 0) {
|
||||
notReviewed++
|
||||
} else if (submitted === assignments.length) {
|
||||
fullyReviewed++
|
||||
} else {
|
||||
partiallyReviewed++
|
||||
}
|
||||
}
|
||||
|
||||
const result = []
|
||||
if (fullyReviewed > 0) result.push({ status: 'FULLY_REVIEWED', count: fullyReviewed })
|
||||
if (partiallyReviewed > 0) result.push({ status: 'PARTIALLY_REVIEWED', count: partiallyReviewed })
|
||||
if (notReviewed > 0) result.push({ status: 'NOT_REVIEWED', count: notReviewed })
|
||||
return result
|
||||
}
|
||||
|
||||
// Non-evaluation rounds: use ProjectRoundState
|
||||
const states = await ctx.prisma.projectRoundState.groupBy({
|
||||
by: ['state'],
|
||||
where: { roundId: input.roundId },
|
||||
@@ -668,7 +723,7 @@ export const analyticsRouter = router({
|
||||
|
||||
const [
|
||||
programCount,
|
||||
activeRoundCount,
|
||||
activeRounds,
|
||||
projectCount,
|
||||
jurorCount,
|
||||
submittedEvaluations,
|
||||
@@ -676,12 +731,11 @@ export const analyticsRouter = router({
|
||||
evaluationScores,
|
||||
] = await Promise.all([
|
||||
ctx.prisma.program.count(),
|
||||
roundId
|
||||
? ctx.prisma.round.findUnique({ where: { id: roundId }, select: { competitionId: true } })
|
||||
.then((r) => r?.competitionId
|
||||
? ctx.prisma.round.count({ where: { competitionId: r.competitionId, status: 'ROUND_ACTIVE' } })
|
||||
: ctx.prisma.round.count({ where: { status: 'ROUND_ACTIVE' } }))
|
||||
: ctx.prisma.round.count({ where: { status: 'ROUND_ACTIVE' } }),
|
||||
ctx.prisma.round.findMany({
|
||||
where: { status: 'ROUND_ACTIVE' },
|
||||
select: { id: true, name: true },
|
||||
take: 5,
|
||||
}),
|
||||
ctx.prisma.project.count({ where: projectFilter }),
|
||||
roundId
|
||||
? ctx.prisma.assignment.findMany({
|
||||
@@ -716,7 +770,8 @@ export const analyticsRouter = router({
|
||||
|
||||
return {
|
||||
programCount,
|
||||
activeRoundCount,
|
||||
activeRoundCount: activeRounds.length,
|
||||
activeRoundName: activeRounds.length === 1 ? activeRounds[0].name : null,
|
||||
projectCount,
|
||||
jurorCount,
|
||||
submittedEvaluations,
|
||||
@@ -1551,7 +1606,12 @@ export const analyticsRouter = router({
|
||||
skip,
|
||||
take: perPage,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
select: {
|
||||
id: true,
|
||||
outcome: true,
|
||||
finalOutcome: true,
|
||||
aiScreeningJson: true,
|
||||
overrideReason: true,
|
||||
project: {
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
Reference in New Issue
Block a user