Fix build errors: add missing Prisma models/fields and resolve TypeScript type errors
Build and Push Docker Image / build (push) Successful in 11m24s Details

Schema: Add 11 new models (RoundTemplate, MentorNote, MentorMilestone,
MentorMilestoneCompletion, EvaluationDiscussion, DiscussionComment,
Message, MessageRecipient, MessageTemplate, Webhook, WebhookDelivery,
DigestLog) and missing fields on existing models (Project.isDraft,
ProjectFile.version, LiveVotingSession.allowAudienceVotes, User.digestFrequency,
AuditLog.sessionId, MentorAssignment.completionStatus, etc).
Add AUDIT_CONFIG/LOCALIZATION/DIGEST/ANALYTICS enum values.

Code: Fix implicit any types, route type casts, enum casts, null safety,
composite key handling, and relation field names across 11 source files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-02-08 14:04:02 +01:00
parent 24fdd2f6be
commit 04d0deced1
12 changed files with 383 additions and 51 deletions

View File

@ -110,6 +110,10 @@ enum SettingCategory {
SECURITY SECURITY
DEFAULTS DEFAULTS
WHATSAPP WHATSAPP
AUDIT_CONFIG
LOCALIZATION
DIGEST
ANALYTICS
} }
enum NotificationChannel { enum NotificationChannel {
@ -222,6 +226,11 @@ model User {
inviteToken String? @unique inviteToken String? @unique
inviteTokenExpiresAt DateTime? inviteTokenExpiresAt DateTime?
// Digest & availability preferences
digestFrequency String? // 'none' | 'daily' | 'weekly'
preferredWorkload Int?
availabilityJson Json? @db.JsonB // { startDate?: string, endDate?: string }
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
lastLoginAt DateTime? lastLoginAt DateTime?
@ -272,6 +281,30 @@ model User {
// Wizard templates // Wizard templates
wizardTemplates WizardTemplate[] @relation("WizardTemplateCreatedBy") wizardTemplates WizardTemplate[] @relation("WizardTemplateCreatedBy")
// Round templates
roundTemplates RoundTemplate[] @relation("RoundTemplateCreatedBy")
// Mentor notes
mentorNotes MentorNote[] @relation("MentorNoteAuthor")
// Milestone completions
milestoneCompletions MentorMilestoneCompletion[] @relation("MilestoneCompletedBy")
// Evaluation discussions
closedDiscussions EvaluationDiscussion[] @relation("DiscussionClosedBy")
discussionComments DiscussionComment[] @relation("DiscussionCommentAuthor")
// Messaging
sentMessages Message[] @relation("MessageSender")
receivedMessages MessageRecipient[] @relation("MessageRecipient")
messageTemplates MessageTemplate[] @relation("MessageTemplateCreator")
// Webhooks
webhooks Webhook[] @relation("WebhookCreator")
// Digest logs
digestLogs DigestLog[] @relation("DigestLog")
// NextAuth relations // NextAuth relations
accounts Account[] accounts Account[]
sessions Session[] sessions Session[]
@ -344,6 +377,8 @@ model Program {
specialAwards SpecialAward[] specialAwards SpecialAward[]
taggingJobs TaggingJob[] taggingJobs TaggingJob[]
wizardTemplates WizardTemplate[] wizardTemplates WizardTemplate[]
roundTemplates RoundTemplate[]
mentorMilestones MentorMilestone[]
@@unique([name, year]) @@unique([name, year])
@@index([status]) @@index([status])
@ -415,6 +450,8 @@ model Round {
taggingJobs TaggingJob[] taggingJobs TaggingJob[]
reminderLogs ReminderLog[] reminderLogs ReminderLog[]
projectFiles ProjectFile[] projectFiles ProjectFile[]
evaluationDiscussions EvaluationDiscussion[]
messages Message[]
@@index([programId]) @@index([programId])
@@index([status]) @@index([status])
@ -499,6 +536,11 @@ model Project {
logoKey String? // Storage key (e.g., "logos/project456/1234567890.png") logoKey String? // Storage key (e.g., "logos/project456/1234567890.png")
logoProvider String? // Storage provider used: 's3' or 'local' logoProvider String? // Storage provider used: 's3' or 'local'
// Draft support
isDraft Boolean @default(false)
draftDataJson Json? @db.JsonB // Form data for drafts
draftExpiresAt DateTime?
// Flexible fields // Flexible fields
tags String[] @default([]) // "Ocean Conservation", "Tech", etc. tags String[] @default([]) // "Ocean Conservation", "Tech", etc.
metadataJson Json? @db.JsonB // Custom fields from Typeform, etc. metadataJson Json? @db.JsonB // Custom fields from Typeform, etc.
@ -523,6 +565,7 @@ model Project {
statusHistory ProjectStatusHistory[] statusHistory ProjectStatusHistory[]
mentorMessages MentorMessage[] mentorMessages MentorMessage[]
evaluationSummaries EvaluationSummary[] evaluationSummaries EvaluationSummary[]
evaluationDiscussions EvaluationDiscussion[]
@@index([programId]) @@index([programId])
@@index([roundId]) @@index([roundId])
@ -553,11 +596,17 @@ model ProjectFile {
isLate Boolean @default(false) // Uploaded after round deadline isLate Boolean @default(false) // Uploaded after round deadline
// Versioning
version Int @default(1)
replacedById String? // FK to the newer file that replaced this one
createdAt DateTime @default(now()) createdAt DateTime @default(now())
// Relations // Relations
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
round Round? @relation(fields: [roundId], references: [id]) round Round? @relation(fields: [roundId], references: [id])
replacedBy ProjectFile? @relation("FileVersions", fields: [replacedById], references: [id], onDelete: SetNull)
replacements ProjectFile[] @relation("FileVersions")
@@unique([bucket, objectKey]) @@unique([bucket, objectKey])
@@index([projectId]) @@index([projectId])
@ -705,6 +754,7 @@ model AuditLog {
// Request info // Request info
ipAddress String? ipAddress String?
userAgent String? userAgent String?
sessionId String?
timestamp DateTime @default(now()) timestamp DateTime @default(now())
@ -716,6 +766,7 @@ model AuditLog {
@@index([entityType, entityId]) @@index([entityType, entityId])
@@index([timestamp]) @@index([timestamp])
@@index([entityType, entityId, timestamp]) @@index([entityType, entityId, timestamp])
@@index([sessionId])
} }
// ============================================================================= // =============================================================================
@ -978,6 +1029,12 @@ model LiveVotingSession {
votingEndsAt DateTime? votingEndsAt DateTime?
projectOrderJson Json? @db.JsonB // Array of project IDs in presentation order projectOrderJson Json? @db.JsonB // Array of project IDs in presentation order
// Audience & presentation settings
allowAudienceVotes Boolean @default(false)
audienceVoteWeight Float? // 0.0 to 1.0
tieBreakerMethod String? // 'admin_decides' | 'highest_individual' | 'revote'
presentationSettingsJson Json? @db.JsonB
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@ -994,6 +1051,7 @@ model LiveVote {
projectId String projectId String
userId String userId String
score Int // 1-10 score Int // 1-10
isAudienceVote Boolean @default(false)
votedAt DateTime @default(now()) votedAt DateTime @default(now())
// Relations // Relations
@ -1047,9 +1105,15 @@ model MentorAssignment {
expertiseMatchScore Float? expertiseMatchScore Float?
aiReasoning String? @db.Text aiReasoning String? @db.Text
// Tracking
completionStatus String @default("in_progress") // 'in_progress' | 'completed' | 'paused'
lastViewedAt DateTime?
// Relations // Relations
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
mentor User @relation("MentorAssignments", fields: [mentorId], references: [id]) mentor User @relation("MentorAssignments", fields: [mentorId], references: [id])
notes MentorNote[]
milestoneCompletions MentorMilestoneCompletion[]
@@index([mentorId]) @@index([mentorId])
@@index([method]) @@index([method])
@ -1454,3 +1518,269 @@ model MentorMessage {
@@index([projectId, createdAt]) @@index([projectId, createdAt])
} }
// =============================================================================
// ROUND TEMPLATES
// =============================================================================
model RoundTemplate {
id String @id @default(cuid())
name String
description String?
programId String?
roundType RoundType @default(EVALUATION)
criteriaJson Json @db.JsonB
settingsJson Json? @db.JsonB
assignmentConfig Json? @db.JsonB
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
program Program? @relation(fields: [programId], references: [id], onDelete: Cascade)
creator User @relation("RoundTemplateCreatedBy", fields: [createdBy], references: [id])
@@index([programId])
@@index([roundType])
}
// =============================================================================
// MENTOR NOTES & MILESTONES
// =============================================================================
model MentorNote {
id String @id @default(cuid())
mentorAssignmentId String
authorId String
content String @db.Text
isVisibleToAdmin Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
mentorAssignment MentorAssignment @relation(fields: [mentorAssignmentId], references: [id], onDelete: Cascade)
author User @relation("MentorNoteAuthor", fields: [authorId], references: [id])
@@index([mentorAssignmentId])
@@index([authorId])
}
model MentorMilestone {
id String @id @default(cuid())
programId String
name String
description String? @db.Text
isRequired Boolean @default(false)
deadlineOffsetDays Int?
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
completions MentorMilestoneCompletion[]
@@index([programId])
@@index([sortOrder])
}
model MentorMilestoneCompletion {
milestoneId String
mentorAssignmentId String
completedById String
completedAt DateTime @default(now())
// Relations
milestone MentorMilestone @relation(fields: [milestoneId], references: [id], onDelete: Cascade)
mentorAssignment MentorAssignment @relation(fields: [mentorAssignmentId], references: [id], onDelete: Cascade)
completedBy User @relation("MilestoneCompletedBy", fields: [completedById], references: [id])
@@id([milestoneId, mentorAssignmentId])
@@index([mentorAssignmentId])
@@index([completedById])
}
// =============================================================================
// EVALUATION DISCUSSIONS
// =============================================================================
model EvaluationDiscussion {
id String @id @default(cuid())
projectId String
roundId String
status String @default("open") // 'open' | 'closed'
closedAt DateTime?
closedById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
closedBy User? @relation("DiscussionClosedBy", fields: [closedById], references: [id], onDelete: SetNull)
comments DiscussionComment[]
@@unique([projectId, roundId])
@@index([roundId])
@@index([closedById])
}
model DiscussionComment {
id String @id @default(cuid())
discussionId String
userId String
content String @db.Text
createdAt DateTime @default(now())
// Relations
discussion EvaluationDiscussion @relation(fields: [discussionId], references: [id], onDelete: Cascade)
user User @relation("DiscussionCommentAuthor", fields: [userId], references: [id])
@@index([discussionId])
@@index([userId])
}
// =============================================================================
// MESSAGING SYSTEM
// =============================================================================
model Message {
id String @id @default(cuid())
senderId String
recipientType String // 'USER', 'ROLE', 'ROUND_JURY', 'PROGRAM_TEAM', 'ALL'
recipientFilter Json? @db.JsonB
roundId String?
templateId String?
subject String
body String @db.Text
deliveryChannels String[]
scheduledAt DateTime?
sentAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
sender User @relation("MessageSender", fields: [senderId], references: [id], onDelete: Cascade)
round Round? @relation(fields: [roundId], references: [id], onDelete: SetNull)
template MessageTemplate? @relation(fields: [templateId], references: [id], onDelete: SetNull)
recipients MessageRecipient[]
@@index([senderId])
@@index([roundId])
@@index([sentAt])
}
model MessageRecipient {
id String @id @default(cuid())
messageId String
userId String
channel String // 'EMAIL', 'IN_APP', etc.
isRead Boolean @default(false)
readAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
user User @relation("MessageRecipient", fields: [userId], references: [id], onDelete: Cascade)
@@unique([messageId, userId, channel])
@@index([userId])
}
model MessageTemplate {
id String @id @default(cuid())
name String
category String // 'SYSTEM', 'EVALUATION', 'ASSIGNMENT'
subject String
body String @db.Text
variables Json? @db.JsonB
createdById String
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
createdBy User @relation("MessageTemplateCreator", fields: [createdById], references: [id], onDelete: Cascade)
messages Message[]
@@index([category])
@@index([isActive])
}
// =============================================================================
// WEBHOOKS
// =============================================================================
model Webhook {
id String @id @default(cuid())
name String
url String
secret String
events String[]
headers Json? @db.JsonB
maxRetries Int @default(3)
isActive Boolean @default(true)
createdById String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
createdBy User @relation("WebhookCreator", fields: [createdById], references: [id], onDelete: Cascade)
deliveries WebhookDelivery[]
@@index([isActive])
@@index([createdById])
}
model WebhookDelivery {
id String @id @default(cuid())
webhookId String
event String
payload Json @db.JsonB
status String @default("PENDING") // 'PENDING', 'DELIVERED', 'FAILED'
responseStatus Int?
responseBody String? @db.Text
attempts Int @default(0)
lastAttemptAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade)
@@index([webhookId])
@@index([status])
@@index([event])
}
// =============================================================================
// DIGEST LOGS
// =============================================================================
model DigestLog {
id String @id @default(cuid())
userId String
digestType String // 'daily' | 'weekly'
contentJson Json @db.JsonB
sentAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
user User @relation("DigestLog", fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, sentAt])
}

View File

@ -674,7 +674,7 @@ export default function ApplySettingsPage() {
toast.success('Loaded preset: MOPC Classic') toast.success('Loaded preset: MOPC Classic')
return return
} }
const template = templates?.find((t) => t.id === value) const template = templates?.find((t: { id: string; name: string; config: unknown }) => t.id === value)
if (template) { if (template) {
setConfig(template.config as WizardConfig) setConfig(template.config as WizardConfig)
setIsDirty(true) setIsDirty(true)
@ -692,7 +692,7 @@ export default function ApplySettingsPage() {
</SelectItem> </SelectItem>
{templates && templates.length > 0 && ( {templates && templates.length > 0 && (
<> <>
{templates.map((t) => ( {templates.map((t: { id: string; name: string }) => (
<SelectItem key={t.id} value={t.id}> <SelectItem key={t.id} value={t.id}>
{t.name} {t.name}
</SelectItem> </SelectItem>

View File

@ -1,5 +1,6 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import Link from 'next/link' import Link from 'next/link'
import type { Route } from 'next'
import { prisma } from '@/lib/prisma' import { prisma } from '@/lib/prisma'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@ -148,7 +149,7 @@ async function ProgramsContent() {
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link href={`/admin/programs/${program.id}/apply-settings`}> <Link href={`/admin/programs/${program.id}/apply-settings` as Route}>
<Wand2 className="mr-2 h-4 w-4" /> <Wand2 className="mr-2 h-4 w-4" />
Apply Settings Apply Settings
</Link> </Link>
@ -202,7 +203,7 @@ async function ProgramsContent() {
</Link> </Link>
</Button> </Button>
<Button variant="outline" size="sm" className="flex-1" asChild> <Button variant="outline" size="sm" className="flex-1" asChild>
<Link href={`/admin/programs/${program.id}/apply-settings`}> <Link href={`/admin/programs/${program.id}/apply-settings` as Route}>
<Wand2 className="mr-2 h-4 w-4" /> <Wand2 className="mr-2 h-4 w-4" />
Apply Apply
</Link> </Link>

View File

@ -197,7 +197,7 @@ export default function RoundTemplatesPage() {
{/* Templates Grid */} {/* Templates Grid */}
{templates && templates.length > 0 ? ( {templates && templates.length > 0 ? (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{templates.map((template) => { {templates.map((template: typeof templates[number]) => {
const criteria = (template.criteriaJson as Array<unknown>) || [] const criteria = (template.criteriaJson as Array<unknown>) || []
const hasSettings = template.settingsJson && Object.keys(template.settingsJson as object).length > 0 const hasSettings = template.settingsJson && Object.keys(template.settingsJson as object).length > 0

View File

@ -80,7 +80,7 @@ function CreateRoundContent() {
const loadTemplate = (templateId: string) => { const loadTemplate = (templateId: string) => {
if (!templateId || !templates) return if (!templateId || !templates) return
const template = templates.find((t) => t.id === templateId) const template = templates.find((t: { id: string; name: string; roundType: string; settingsJson: unknown }) => t.id === templateId)
if (!template) return if (!template) return
// Apply template settings // Apply template settings
@ -207,7 +207,7 @@ function CreateRoundContent() {
<SelectValue placeholder="Select a template..." /> <SelectValue placeholder="Select a template..." />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{templates.map((t) => ( {templates.map((t: { id: string; name: string; description?: string | null }) => (
<SelectItem key={t.id} value={t.id}> <SelectItem key={t.id} value={t.id}>
{t.name} {t.name}
{t.description ? ` - ${t.description}` : ''} {t.description ? ` - ${t.description}` : ''}

View File

@ -510,11 +510,11 @@ function MilestonesSection({
} }
const completedCount = milestones.filter( const completedCount = milestones.filter(
(m) => m.myCompletions.length > 0 (m: { myCompletions: unknown[] }) => m.myCompletions.length > 0
).length ).length
const totalRequired = milestones.filter((m) => m.isRequired).length const totalRequired = milestones.filter((m: { isRequired: boolean }) => m.isRequired).length
const requiredCompleted = milestones.filter( const requiredCompleted = milestones.filter(
(m) => m.isRequired && m.myCompletions.length > 0 (m: { isRequired: boolean; myCompletions: unknown[] }) => m.isRequired && m.myCompletions.length > 0
).length ).length
const handleToggle = (milestoneId: string, isCompleted: boolean) => { const handleToggle = (milestoneId: string, isCompleted: boolean) => {
@ -545,7 +545,7 @@ function MilestonesSection({
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-3"> <div className="space-y-3">
{milestones.map((milestone) => { {milestones.map((milestone: { id: string; name: string; description: string | null; isRequired: boolean; myCompletions: { completedAt: Date }[] }) => {
const isCompleted = milestone.myCompletions.length > 0 const isCompleted = milestone.myCompletions.length > 0
const isPending = completeMutation.isPending || uncompleteMutation.isPending const isPending = completeMutation.isPending || uncompleteMutation.isPending
@ -752,7 +752,7 @@ function NotesSection({ mentorAssignmentId }: { mentorAssignmentId: string }) {
</div> </div>
) : notes && notes.length > 0 ? ( ) : notes && notes.length > 0 ? (
<div className="space-y-3"> <div className="space-y-3">
{notes.map((note) => ( {notes.map((note: { id: string; content: string; isVisibleToAdmin: boolean; createdAt: Date }) => (
<div <div
key={note.id} key={note.id}
className="p-4 rounded-lg border space-y-2" className="p-4 rounded-lg border space-y-2"

View File

@ -208,9 +208,9 @@ export function MembersContent() {
<TableCell> <TableCell>
<div> <div>
{user.role === 'MENTOR' ? ( {user.role === 'MENTOR' ? (
<p>{user._count.mentorAssignments} mentored</p> <p>{(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.mentorAssignments} mentored</p>
) : ( ) : (
<p>{user._count.assignments} assigned</p> <p>{(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.assignments} assigned</p>
)} )}
</div> </div>
</TableCell> </TableCell>
@ -276,8 +276,8 @@ export function MembersContent() {
<span className="text-muted-foreground">Assignments</span> <span className="text-muted-foreground">Assignments</span>
<span> <span>
{user.role === 'MENTOR' {user.role === 'MENTOR'
? `${user._count.mentorAssignments} mentored` ? `${(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.mentorAssignments} mentored`
: `${user._count.assignments} assigned`} : `${(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.assignments} assigned`}
</span> </span>
</div> </div>
{user.expertiseTags && user.expertiseTags.length > 0 && ( {user.expertiseTags && user.expertiseTags.length > 0 && (

View File

@ -676,6 +676,7 @@ export const applicationRouter = router({
// Create new draft project // Create new draft project
const project = await ctx.prisma.project.create({ const project = await ctx.prisma.project.create({
data: { data: {
programId: round.programId,
roundId: round.id, roundId: round.id,
title: input.title || 'Untitled Draft', title: input.title || 'Untitled Draft',
isDraft: true, isDraft: true,
@ -795,8 +796,8 @@ export const applicationRouter = router({
title: data.projectName, title: data.projectName,
teamName: data.teamName, teamName: data.teamName,
description: data.description, description: data.description,
competitionCategory: data.competitionCategory, competitionCategory: data.competitionCategory as CompetitionCategory,
oceanIssue: data.oceanIssue, oceanIssue: data.oceanIssue as OceanIssue,
country: data.country, country: data.country,
geographicZone: data.city ? `${data.city}, ${data.country}` : data.country, geographicZone: data.city ? `${data.city}, ${data.country}` : data.country,
institution: data.institution, institution: data.institution,
@ -838,7 +839,7 @@ export const applicationRouter = router({
return { return {
success: true, success: true,
projectId: updated.id, projectId: updated.id,
message: `Thank you for applying to ${project.round.program.name}!`, message: `Thank you for applying to ${project.round?.program.name ?? 'the program'}!`,
} }
}), }),

View File

@ -863,7 +863,7 @@ export const evaluationRouter = router({
const settings = (round.settingsJson as Record<string, unknown>) || {} const settings = (round.settingsJson as Record<string, unknown>) || {}
const anonymizationLevel = (settings.anonymization_level as string) || 'fully_anonymous' const anonymizationLevel = (settings.anonymization_level as string) || 'fully_anonymous'
const anonymizedComments = discussion.comments.map((c, idx) => { const anonymizedComments = discussion.comments.map((c: { id: string; userId: string; user: { name: string | null }; content: string; createdAt: Date }, idx: number) => {
let authorLabel: string let authorLabel: string
if (anonymizationLevel === 'named' || c.userId === ctx.user.id) { if (anonymizationLevel === 'named' || c.userId === ctx.user.id) {
authorLabel = c.user.name || 'Juror' authorLabel = c.user.name || 'Juror'
@ -871,7 +871,7 @@ export const evaluationRouter = router({
const name = c.user.name || '' const name = c.user.name || ''
authorLabel = name authorLabel = name
.split(' ') .split(' ')
.map((n) => n[0]) .map((n: string) => n[0])
.join('') .join('')
.toUpperCase() || 'J' .toUpperCase() || 'J'
} else { } else {

View File

@ -405,8 +405,8 @@ export const liveVotingRouter = router({
.map((jurySc) => { .map((jurySc) => {
const project = projects.find((p) => p.id === jurySc.projectId) const project = projects.find((p) => p.id === jurySc.projectId)
const audienceSc = audienceMap.get(jurySc.projectId) const audienceSc = audienceMap.get(jurySc.projectId)
const juryAvg = jurySc._avg.score || 0 const juryAvg = jurySc._avg?.score || 0
const audienceAvg = audienceSc?._avg.score || 0 const audienceAvg = audienceSc?._avg?.score || 0
const weightedTotal = audienceWeight > 0 && audienceSc const weightedTotal = audienceWeight > 0 && audienceSc
? juryAvg * juryWeight + audienceAvg * audienceWeight ? juryAvg * juryWeight + audienceAvg * audienceWeight
: juryAvg : juryAvg

View File

@ -981,9 +981,9 @@ export const mentorRouter = router({
}) })
const myAssignmentIds = new Set(myAssignments.map((a) => a.id)) const myAssignmentIds = new Set(myAssignments.map((a) => a.id))
return milestones.map((milestone) => ({ return milestones.map((milestone: typeof milestones[number]) => ({
...milestone, ...milestone,
myCompletions: milestone.completions.filter((c) => myCompletions: milestone.completions.filter((c: { mentorAssignmentId: string }) =>
myAssignmentIds.has(c.mentorAssignmentId) myAssignmentIds.has(c.mentorAssignmentId)
), ),
})) }))
@ -1036,7 +1036,7 @@ export const mentorRouter = router({
const completedMilestones = await ctx.prisma.mentorMilestoneCompletion.findMany({ const completedMilestones = await ctx.prisma.mentorMilestoneCompletion.findMany({
where: { where: {
mentorAssignmentId: input.mentorAssignmentId, mentorAssignmentId: input.mentorAssignmentId,
milestoneId: { in: requiredMilestones.map((m) => m.id) }, milestoneId: { in: requiredMilestones.map((m: { id: string }) => m.id) },
}, },
select: { milestoneId: true }, select: { milestoneId: true },
}) })
@ -1057,7 +1057,7 @@ export const mentorRouter = router({
userId: ctx.user.id, userId: ctx.user.id,
action: 'COMPLETE_MILESTONE', action: 'COMPLETE_MILESTONE',
entityType: 'MentorMilestoneCompletion', entityType: 'MentorMilestoneCompletion',
entityId: completion.id, entityId: `${completion.milestoneId}_${completion.mentorAssignmentId}`,
detailsJson: { detailsJson: {
milestoneId: input.milestoneId, milestoneId: input.milestoneId,
mentorAssignmentId: input.mentorAssignmentId, mentorAssignmentId: input.mentorAssignmentId,
@ -1243,7 +1243,7 @@ export const mentorRouter = router({
mentor: { select: { id: true, name: true, email: true } }, mentor: { select: { id: true, name: true, email: true } },
project: { select: { id: true, title: true } }, project: { select: { id: true, title: true } },
notes: { select: { id: true } }, notes: { select: { id: true } },
milestoneCompletions: { select: { id: true } }, milestoneCompletions: { select: { milestoneId: true } },
}, },
}) })

View File

@ -247,7 +247,7 @@ export const messageRouter = router({
subject: input.subject, subject: input.subject,
body: input.body, body: input.body,
variables: input.variables ?? undefined, variables: input.variables ?? undefined,
createdBy: ctx.user.id, createdById: ctx.user.id,
}, },
}) })