Fix build errors: add missing Prisma models/fields and resolve TypeScript type errors
Build and Push Docker Image / build (push) Successful in 11m24s
Details
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:
parent
24fdd2f6be
commit
04d0deced1
|
|
@ -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])
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}` : ''}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 && (
|
||||||
|
|
|
||||||
|
|
@ -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'}!`,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 } },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue