Implement Prototype 1 improvements: unified members, project filters, audit expansion, filtering rounds, special awards

- Unified Member Management: merge /admin/users and /admin/mentors into /admin/members with role tabs, search, pagination
- Project List Filters: add search, multi-status filter, round/category/country selects, boolean toggles, URL persistence
- Audit Log Expansion: track logins, round state changes, evaluation submissions, file access, role changes via shared logAudit utility
- Founding Date Field: add foundedAt to Project model with CSV import support
- Filtering Round System: configurable rules (field-based, document check, AI screening), execution engine, results review with override/reinstate
- Special Awards System: named awards with eligibility criteria, dedicated jury, PICK_WINNER/RANKED/SCORED voting modes, AI eligibility
- Dashboard resilience: wrap heavy queries in try-catch to prevent error boundary on transient DB failures
- Reusable pagination component extracted to src/components/shared/pagination.tsx
- Old /admin/users and /admin/mentors routes redirect to /admin/members
- Prisma migration for all schema additions (additive, no data loss)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-02 16:58:29 +01:00
parent 8fda8deded
commit 90e3adfab2
44 changed files with 7268 additions and 2154 deletions

View File

@@ -0,0 +1,221 @@
-- CreateEnum
CREATE TYPE "FilteringOutcome" AS ENUM ('PASSED', 'FILTERED_OUT', 'FLAGGED');
-- CreateEnum
CREATE TYPE "FilteringRuleType" AS ENUM ('FIELD_BASED', 'DOCUMENT_CHECK', 'AI_SCREENING');
-- CreateEnum
CREATE TYPE "AwardScoringMode" AS ENUM ('PICK_WINNER', 'RANKED', 'SCORED');
-- CreateEnum
CREATE TYPE "AwardStatus" AS ENUM ('DRAFT', 'NOMINATIONS_OPEN', 'VOTING_OPEN', 'CLOSED', 'ARCHIVED');
-- CreateEnum
CREATE TYPE "EligibilityMethod" AS ENUM ('AUTO', 'MANUAL');
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "foundedAt" TIMESTAMP(3);
-- AlterTable
ALTER TABLE "User" ADD COLUMN "inviteToken" TEXT,
ADD COLUMN "inviteTokenExpiresAt" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "FilteringRule" (
"id" TEXT NOT NULL,
"roundId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"ruleType" "FilteringRuleType" NOT NULL,
"configJson" JSONB NOT NULL,
"priority" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FilteringRule_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FilteringResult" (
"id" TEXT NOT NULL,
"roundId" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"outcome" "FilteringOutcome" NOT NULL,
"ruleResultsJson" JSONB,
"aiScreeningJson" JSONB,
"overriddenBy" TEXT,
"overriddenAt" TIMESTAMP(3),
"overrideReason" TEXT,
"finalOutcome" "FilteringOutcome",
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FilteringResult_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SpecialAward" (
"id" TEXT NOT NULL,
"programId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"status" "AwardStatus" NOT NULL DEFAULT 'DRAFT',
"criteriaText" TEXT,
"autoTagRulesJson" JSONB,
"scoringMode" "AwardScoringMode" NOT NULL DEFAULT 'PICK_WINNER',
"maxRankedPicks" INTEGER,
"votingStartAt" TIMESTAMP(3),
"votingEndAt" TIMESTAMP(3),
"evaluationFormId" TEXT,
"winnerProjectId" TEXT,
"winnerOverridden" BOOLEAN NOT NULL DEFAULT false,
"winnerOverriddenBy" TEXT,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SpecialAward_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AwardEligibility" (
"id" TEXT NOT NULL,
"awardId" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"method" "EligibilityMethod" NOT NULL DEFAULT 'AUTO',
"eligible" BOOLEAN NOT NULL DEFAULT false,
"aiReasoningJson" JSONB,
"overriddenBy" TEXT,
"overriddenAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AwardEligibility_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AwardJuror" (
"id" TEXT NOT NULL,
"awardId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AwardJuror_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AwardVote" (
"id" TEXT NOT NULL,
"awardId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"rank" INTEGER,
"votedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AwardVote_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "FilteringRule_roundId_idx" ON "FilteringRule"("roundId");
-- CreateIndex
CREATE INDEX "FilteringRule_priority_idx" ON "FilteringRule"("priority");
-- CreateIndex
CREATE INDEX "FilteringResult_roundId_idx" ON "FilteringResult"("roundId");
-- CreateIndex
CREATE INDEX "FilteringResult_projectId_idx" ON "FilteringResult"("projectId");
-- CreateIndex
CREATE INDEX "FilteringResult_outcome_idx" ON "FilteringResult"("outcome");
-- CreateIndex
CREATE UNIQUE INDEX "FilteringResult_roundId_projectId_key" ON "FilteringResult"("roundId", "projectId");
-- CreateIndex
CREATE INDEX "SpecialAward_programId_idx" ON "SpecialAward"("programId");
-- CreateIndex
CREATE INDEX "SpecialAward_status_idx" ON "SpecialAward"("status");
-- CreateIndex
CREATE INDEX "SpecialAward_sortOrder_idx" ON "SpecialAward"("sortOrder");
-- CreateIndex
CREATE INDEX "AwardEligibility_awardId_idx" ON "AwardEligibility"("awardId");
-- CreateIndex
CREATE INDEX "AwardEligibility_projectId_idx" ON "AwardEligibility"("projectId");
-- CreateIndex
CREATE INDEX "AwardEligibility_eligible_idx" ON "AwardEligibility"("eligible");
-- CreateIndex
CREATE UNIQUE INDEX "AwardEligibility_awardId_projectId_key" ON "AwardEligibility"("awardId", "projectId");
-- CreateIndex
CREATE INDEX "AwardJuror_awardId_idx" ON "AwardJuror"("awardId");
-- CreateIndex
CREATE INDEX "AwardJuror_userId_idx" ON "AwardJuror"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "AwardJuror_awardId_userId_key" ON "AwardJuror"("awardId", "userId");
-- CreateIndex
CREATE INDEX "AwardVote_awardId_idx" ON "AwardVote"("awardId");
-- CreateIndex
CREATE INDEX "AwardVote_userId_idx" ON "AwardVote"("userId");
-- CreateIndex
CREATE INDEX "AwardVote_projectId_idx" ON "AwardVote"("projectId");
-- CreateIndex
CREATE UNIQUE INDEX "AwardVote_awardId_userId_projectId_key" ON "AwardVote"("awardId", "userId", "projectId");
-- CreateIndex
CREATE UNIQUE INDEX "User_inviteToken_key" ON "User"("inviteToken");
-- AddForeignKey
ALTER TABLE "FilteringRule" ADD CONSTRAINT "FilteringRule_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_overriddenBy_fkey" FOREIGN KEY ("overriddenBy") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_winnerProjectId_fkey" FOREIGN KEY ("winnerProjectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AwardEligibility" ADD CONSTRAINT "AwardEligibility_awardId_fkey" FOREIGN KEY ("awardId") REFERENCES "SpecialAward"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AwardEligibility" ADD CONSTRAINT "AwardEligibility_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AwardEligibility" ADD CONSTRAINT "AwardEligibility_overriddenBy_fkey" FOREIGN KEY ("overriddenBy") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AwardJuror" ADD CONSTRAINT "AwardJuror_awardId_fkey" FOREIGN KEY ("awardId") REFERENCES "SpecialAward"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AwardJuror" ADD CONSTRAINT "AwardJuror_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AwardVote" ADD CONSTRAINT "AwardVote_awardId_fkey" FOREIGN KEY ("awardId") REFERENCES "SpecialAward"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AwardVote" ADD CONSTRAINT "AwardVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AwardVote" ADD CONSTRAINT "AwardVote_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"