Implement 10 platform features: evaluation UX, admin tools, AI summaries, applicant portal
Batch 1 - Quick Wins: - F1: Evaluation progress indicator with touch tracking in sticky status bar - F2: Export filtering results as CSV with dynamic AI column flattening - F3: Observer access to analytics dashboards (8 procedures changed to observerProcedure) Batch 2 - Jury Experience: - F4: Countdown timer component with urgency colors + email reminder service with cron endpoint - F5: Conflict of interest declaration system (dialog, admin management, review workflow) Batch 3 - Admin & AI Enhancements: - F6: Bulk status update UI with selection checkboxes, floating toolbar, status history recording - F7: AI-powered evaluation summary with anonymized data, OpenAI integration, scoring patterns - F8: Smart assignment improvements (geo diversity penalty, round familiarity bonus, COI blocking) Batch 4 - Form Flexibility & Applicant Portal: - F9: Evaluation form flexibility (text, boolean, section_header types, conditional visibility) - F10: Applicant portal (status timeline, per-round documents, mentor messaging) Schema: 5 new models (ReminderLog, ConflictOfInterest, EvaluationSummary, ProjectStatusHistory, MentorMessage), ProjectFile extended with roundId + isLate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -635,6 +635,109 @@ export const mentorRouter = router({
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Send a message to the project team (mentor side)
|
||||
*/
|
||||
sendMessage: mentorProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
message: z.string().min(1).max(5000),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// Verify the mentor is assigned to this project
|
||||
const assignment = await ctx.prisma.mentorAssignment.findFirst({
|
||||
where: {
|
||||
projectId: input.projectId,
|
||||
mentorId: ctx.user.id,
|
||||
},
|
||||
})
|
||||
|
||||
const isAdmin = ['SUPER_ADMIN', 'PROGRAM_ADMIN'].includes(ctx.user.role)
|
||||
|
||||
if (!assignment && !isAdmin) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: 'You are not assigned to mentor this project',
|
||||
})
|
||||
}
|
||||
|
||||
const mentorMessage = await ctx.prisma.mentorMessage.create({
|
||||
data: {
|
||||
projectId: input.projectId,
|
||||
senderId: ctx.user.id,
|
||||
message: input.message,
|
||||
},
|
||||
include: {
|
||||
sender: {
|
||||
select: { id: true, name: true, email: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Notify project team members
|
||||
await notifyProjectTeam(input.projectId, {
|
||||
type: 'MENTOR_MESSAGE',
|
||||
title: 'New Message from Mentor',
|
||||
message: `${ctx.user.name || 'Your mentor'} sent you a message`,
|
||||
linkUrl: `/my-submission/${input.projectId}`,
|
||||
linkLabel: 'View Message',
|
||||
priority: 'normal',
|
||||
metadata: {
|
||||
projectId: input.projectId,
|
||||
},
|
||||
})
|
||||
|
||||
return mentorMessage
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get messages for a project (mentor side)
|
||||
*/
|
||||
getMessages: mentorProcedure
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
// Verify the mentor is assigned to this project
|
||||
const assignment = await ctx.prisma.mentorAssignment.findFirst({
|
||||
where: {
|
||||
projectId: input.projectId,
|
||||
mentorId: ctx.user.id,
|
||||
},
|
||||
})
|
||||
|
||||
const isAdmin = ['SUPER_ADMIN', 'PROGRAM_ADMIN'].includes(ctx.user.role)
|
||||
|
||||
if (!assignment && !isAdmin) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: 'You are not assigned to mentor this project',
|
||||
})
|
||||
}
|
||||
|
||||
const messages = await ctx.prisma.mentorMessage.findMany({
|
||||
where: { projectId: input.projectId },
|
||||
include: {
|
||||
sender: {
|
||||
select: { id: true, name: true, email: true, role: true },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
})
|
||||
|
||||
// Mark unread messages from the team as read
|
||||
await ctx.prisma.mentorMessage.updateMany({
|
||||
where: {
|
||||
projectId: input.projectId,
|
||||
senderId: { not: ctx.user.id },
|
||||
isRead: false,
|
||||
},
|
||||
data: { isRead: true },
|
||||
})
|
||||
|
||||
return messages
|
||||
}),
|
||||
|
||||
/**
|
||||
* List all mentor assignments (admin)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user