Add background filtering jobs, improved date picker, AI reasoning display
- Implement background job system for AI filtering to avoid HTTP timeouts - Add FilteringJob model to track progress of long-running filtering operations - Add real-time progress polling for filtering operations on round details page - Create custom DateTimePicker component with calendar popup (no year picker hassle) - Fix round date persistence bug (refetchOnWindowFocus was resetting form state) - Integrate filtering controls into round details page for filtering rounds - Display AI reasoning for flagged/filtered projects in results table - Add onboarding system scaffolding (schema, routes, basic UI) - Allow setting round dates in the past for manual overrides Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
-- Add Onboarding System Schema Changes
|
||||
-- This migration adds the onboarding configuration system for the public application wizard
|
||||
|
||||
-- CreateEnum: SpecialFieldType
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'SpecialFieldType') THEN
|
||||
CREATE TYPE "SpecialFieldType" AS ENUM (
|
||||
'TEAM_MEMBERS',
|
||||
'COMPETITION_CATEGORY',
|
||||
'OCEAN_ISSUE',
|
||||
'FILE_UPLOAD',
|
||||
'GDPR_CONSENT',
|
||||
'COUNTRY_SELECT'
|
||||
);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- CreateTable: OnboardingStep
|
||||
CREATE TABLE IF NOT EXISTS "OnboardingStep" (
|
||||
"id" TEXT NOT NULL,
|
||||
"formId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isOptional" BOOLEAN NOT NULL DEFAULT false,
|
||||
"conditionJson" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "OnboardingStep_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Add columns to ApplicationForm
|
||||
DO $$
|
||||
BEGIN
|
||||
-- roundId column (unique)
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'ApplicationForm' AND column_name = 'roundId'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationForm" ADD COLUMN "roundId" TEXT;
|
||||
END IF;
|
||||
|
||||
-- sendConfirmationEmail column
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'ApplicationForm' AND column_name = 'sendConfirmationEmail'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationForm" ADD COLUMN "sendConfirmationEmail" BOOLEAN NOT NULL DEFAULT true;
|
||||
END IF;
|
||||
|
||||
-- sendTeamInviteEmails column
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'ApplicationForm' AND column_name = 'sendTeamInviteEmails'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationForm" ADD COLUMN "sendTeamInviteEmails" BOOLEAN NOT NULL DEFAULT true;
|
||||
END IF;
|
||||
|
||||
-- confirmationEmailSubject column
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'ApplicationForm' AND column_name = 'confirmationEmailSubject'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationForm" ADD COLUMN "confirmationEmailSubject" TEXT;
|
||||
END IF;
|
||||
|
||||
-- confirmationEmailBody column
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'ApplicationForm' AND column_name = 'confirmationEmailBody'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationForm" ADD COLUMN "confirmationEmailBody" TEXT;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Add columns to ApplicationFormField
|
||||
DO $$
|
||||
BEGIN
|
||||
-- stepId column
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'ApplicationFormField' AND column_name = 'stepId'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationFormField" ADD COLUMN "stepId" TEXT;
|
||||
END IF;
|
||||
|
||||
-- projectMapping column
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'ApplicationFormField' AND column_name = 'projectMapping'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationFormField" ADD COLUMN "projectMapping" TEXT;
|
||||
END IF;
|
||||
|
||||
-- specialType column
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'ApplicationFormField' AND column_name = 'specialType'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationFormField" ADD COLUMN "specialType" "SpecialFieldType";
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Create indexes for OnboardingStep
|
||||
CREATE INDEX IF NOT EXISTS "OnboardingStep_formId_idx" ON "OnboardingStep"("formId");
|
||||
CREATE INDEX IF NOT EXISTS "OnboardingStep_sortOrder_idx" ON "OnboardingStep"("sortOrder");
|
||||
|
||||
-- Create index for ApplicationForm.roundId
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "ApplicationForm_roundId_key" ON "ApplicationForm"("roundId");
|
||||
CREATE INDEX IF NOT EXISTS "ApplicationForm_roundId_idx" ON "ApplicationForm"("roundId");
|
||||
|
||||
-- Create index for ApplicationFormField.stepId
|
||||
CREATE INDEX IF NOT EXISTS "ApplicationFormField_stepId_idx" ON "ApplicationFormField"("stepId");
|
||||
|
||||
-- Add foreign key constraints
|
||||
DO $$
|
||||
BEGIN
|
||||
-- OnboardingStep -> ApplicationForm
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'OnboardingStep_formId_fkey'
|
||||
) THEN
|
||||
ALTER TABLE "OnboardingStep" ADD CONSTRAINT "OnboardingStep_formId_fkey"
|
||||
FOREIGN KEY ("formId") REFERENCES "ApplicationForm"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
END IF;
|
||||
|
||||
-- ApplicationFormField -> OnboardingStep
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'ApplicationFormField_stepId_fkey'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationFormField" ADD CONSTRAINT "ApplicationFormField_stepId_fkey"
|
||||
FOREIGN KEY ("stepId") REFERENCES "OnboardingStep"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
END IF;
|
||||
|
||||
-- ApplicationForm -> Round
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'ApplicationForm_roundId_fkey'
|
||||
) THEN
|
||||
ALTER TABLE "ApplicationForm" ADD CONSTRAINT "ApplicationForm_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -0,0 +1,38 @@
|
||||
-- Create FilteringJobStatus enum if not exists
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "FilteringJobStatus" AS ENUM ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- Create FilteringJob table if not exists
|
||||
CREATE TABLE IF NOT EXISTS "FilteringJob" (
|
||||
"id" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"status" "FilteringJobStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"totalProjects" INTEGER NOT NULL DEFAULT 0,
|
||||
"totalBatches" INTEGER NOT NULL DEFAULT 0,
|
||||
"currentBatch" INTEGER NOT NULL DEFAULT 0,
|
||||
"processedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"passedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"filteredCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"flaggedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"errorMessage" TEXT,
|
||||
"startedAt" TIMESTAMP(3),
|
||||
"completedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FilteringJob_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Add indexes
|
||||
CREATE INDEX IF NOT EXISTS "FilteringJob_roundId_idx" ON "FilteringJob"("roundId");
|
||||
CREATE INDEX IF NOT EXISTS "FilteringJob_status_idx" ON "FilteringJob"("status");
|
||||
|
||||
-- Add foreign key if not exists
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "FilteringJob" ADD CONSTRAINT "FilteringJob_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
@@ -167,6 +167,15 @@ enum FormFieldType {
|
||||
INSTRUCTIONS
|
||||
}
|
||||
|
||||
enum SpecialFieldType {
|
||||
TEAM_MEMBERS // Team member repeater
|
||||
COMPETITION_CATEGORY // Business Concept vs Startup
|
||||
OCEAN_ISSUE // Ocean issue dropdown
|
||||
FILE_UPLOAD // File upload
|
||||
GDPR_CONSENT // GDPR consent checkbox
|
||||
COUNTRY_SELECT // Country dropdown
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// APPLICANT SYSTEM ENUMS
|
||||
// =============================================================================
|
||||
@@ -376,14 +385,16 @@ model Round {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
||||
roundProjects RoundProject[]
|
||||
assignments Assignment[]
|
||||
evaluationForms EvaluationForm[]
|
||||
gracePeriods GracePeriod[]
|
||||
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
||||
roundProjects RoundProject[]
|
||||
assignments Assignment[]
|
||||
evaluationForms EvaluationForm[]
|
||||
gracePeriods GracePeriod[]
|
||||
liveVotingSession LiveVotingSession?
|
||||
filteringRules FilteringRule[]
|
||||
filteringResults FilteringResult[]
|
||||
filteringRules FilteringRule[]
|
||||
filteringResults FilteringResult[]
|
||||
filteringJobs FilteringJob[]
|
||||
applicationForm ApplicationForm?
|
||||
|
||||
@@index([programId])
|
||||
@@index([status])
|
||||
@@ -858,22 +869,35 @@ model ApplicationForm {
|
||||
|
||||
confirmationMessage String? @db.Text
|
||||
|
||||
// Round linking (for onboarding forms that create projects)
|
||||
roundId String? @unique
|
||||
|
||||
// Email settings
|
||||
sendConfirmationEmail Boolean @default(true)
|
||||
sendTeamInviteEmails Boolean @default(true)
|
||||
confirmationEmailSubject String?
|
||||
confirmationEmailBody String? @db.Text
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
program Program? @relation(fields: [programId], references: [id], onDelete: SetNull)
|
||||
round Round? @relation(fields: [roundId], references: [id], onDelete: SetNull)
|
||||
fields ApplicationFormField[]
|
||||
steps OnboardingStep[]
|
||||
submissions ApplicationFormSubmission[]
|
||||
|
||||
@@index([programId])
|
||||
@@index([status])
|
||||
@@index([isPublic])
|
||||
@@index([roundId])
|
||||
}
|
||||
|
||||
model ApplicationFormField {
|
||||
id String @id @default(cuid())
|
||||
formId String
|
||||
stepId String? // Which step this field belongs to (for onboarding)
|
||||
fieldType FormFieldType
|
||||
name String // Internal name (e.g., "project_title")
|
||||
label String // Display label (e.g., "Project Title")
|
||||
@@ -889,6 +913,10 @@ model ApplicationFormField {
|
||||
optionsJson Json? @db.JsonB // For select/radio: [{ value, label }]
|
||||
conditionJson Json? @db.JsonB // Conditional logic: { fieldId, operator, value }
|
||||
|
||||
// Onboarding-specific fields
|
||||
projectMapping String? // Maps to Project column: "title", "description", etc.
|
||||
specialType SpecialFieldType? // Special handling for complex fields
|
||||
|
||||
sortOrder Int @default(0)
|
||||
width String @default("full") // full, half
|
||||
|
||||
@@ -896,7 +924,30 @@ model ApplicationFormField {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
form ApplicationForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
form ApplicationForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
step OnboardingStep? @relation(fields: [stepId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([formId])
|
||||
@@index([stepId])
|
||||
@@index([sortOrder])
|
||||
}
|
||||
|
||||
model OnboardingStep {
|
||||
id String @id @default(cuid())
|
||||
formId String
|
||||
name String // Internal identifier (e.g., "category", "contact")
|
||||
title String // Display title (e.g., "Category", "Contact Information")
|
||||
description String? @db.Text
|
||||
sortOrder Int @default(0)
|
||||
isOptional Boolean @default(false)
|
||||
conditionJson Json? @db.JsonB // Conditional visibility: { fieldId, operator, value }
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
form ApplicationForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
fields ApplicationFormField[]
|
||||
|
||||
@@index([formId])
|
||||
@@index([sortOrder])
|
||||
@@ -1117,6 +1168,39 @@ model FilteringResult {
|
||||
@@index([outcome])
|
||||
}
|
||||
|
||||
// Tracks progress of long-running filtering jobs
|
||||
model FilteringJob {
|
||||
id String @id @default(cuid())
|
||||
roundId String
|
||||
status FilteringJobStatus @default(PENDING)
|
||||
totalProjects Int @default(0)
|
||||
totalBatches Int @default(0)
|
||||
currentBatch Int @default(0)
|
||||
processedCount Int @default(0)
|
||||
passedCount Int @default(0)
|
||||
filteredCount Int @default(0)
|
||||
flaggedCount Int @default(0)
|
||||
errorMessage String? @db.Text
|
||||
startedAt DateTime?
|
||||
completedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([roundId])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
enum FilteringJobStatus {
|
||||
PENDING
|
||||
RUNNING
|
||||
COMPLETED
|
||||
FAILED
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SPECIAL AWARDS SYSTEM
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user