2026-01-30 13:41:32 +01:00
|
|
|
import { Suspense } from 'react'
|
|
|
|
|
import Link from 'next/link'
|
Fix build errors: add missing Prisma models/fields and resolve TypeScript type errors
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>
2026-02-08 14:04:02 +01:00
|
|
|
import type { Route } from 'next'
|
2026-01-30 13:41:32 +01:00
|
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
|
|
|
|
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from '@/components/ui/card'
|
|
|
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
|
|
|
import {
|
|
|
|
|
Table,
|
|
|
|
|
TableBody,
|
|
|
|
|
TableCell,
|
|
|
|
|
TableHead,
|
|
|
|
|
TableHeader,
|
|
|
|
|
TableRow,
|
|
|
|
|
} from '@/components/ui/table'
|
|
|
|
|
import {
|
|
|
|
|
DropdownMenu,
|
|
|
|
|
DropdownMenuContent,
|
|
|
|
|
DropdownMenuItem,
|
|
|
|
|
DropdownMenuTrigger,
|
|
|
|
|
} from '@/components/ui/dropdown-menu'
|
|
|
|
|
import {
|
|
|
|
|
Plus,
|
|
|
|
|
MoreHorizontal,
|
|
|
|
|
FolderKanban,
|
|
|
|
|
Eye,
|
|
|
|
|
Pencil,
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
Wand2,
|
2026-01-30 13:41:32 +01:00
|
|
|
} from 'lucide-react'
|
|
|
|
|
import { formatDateOnly } from '@/lib/utils'
|
|
|
|
|
|
|
|
|
|
async function ProgramsContent() {
|
|
|
|
|
const programs = await prisma.program.findMany({
|
|
|
|
|
// Note: PROGRAM_ADMIN filtering should be handled via middleware or a separate relation
|
|
|
|
|
include: {
|
|
|
|
|
_count: {
|
|
|
|
|
select: {
|
|
|
|
|
rounds: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
rounds: {
|
|
|
|
|
where: { status: 'ACTIVE' },
|
|
|
|
|
select: { id: true },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: { createdAt: 'desc' },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (programs.length === 0) {
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
|
|
|
<FolderKanban className="h-12 w-12 text-muted-foreground/50" />
|
|
|
|
|
<p className="mt-2 font-medium">No programs yet</p>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
Create your first program to start managing projects and rounds
|
|
|
|
|
</p>
|
|
|
|
|
<Button asChild className="mt-4">
|
|
|
|
|
<Link href="/admin/programs/new">
|
|
|
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
|
|
|
Create Program
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const statusColors: Record<string, 'default' | 'success' | 'secondary' | 'destructive'> = {
|
|
|
|
|
ACTIVE: 'default',
|
|
|
|
|
COMPLETED: 'success',
|
|
|
|
|
DRAFT: 'secondary',
|
|
|
|
|
ARCHIVED: 'secondary',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* Desktop table view */}
|
|
|
|
|
<Card className="hidden md:block">
|
|
|
|
|
<Table>
|
|
|
|
|
<TableHeader>
|
|
|
|
|
<TableRow>
|
|
|
|
|
<TableHead>Program</TableHead>
|
|
|
|
|
<TableHead>Year</TableHead>
|
|
|
|
|
<TableHead>Rounds</TableHead>
|
|
|
|
|
<TableHead>Status</TableHead>
|
|
|
|
|
<TableHead>Created</TableHead>
|
|
|
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
|
|
|
</TableRow>
|
|
|
|
|
</TableHeader>
|
|
|
|
|
<TableBody>
|
|
|
|
|
{programs.map((program) => (
|
|
|
|
|
<TableRow key={program.id}>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-medium">{program.name}</p>
|
|
|
|
|
{program.description && (
|
|
|
|
|
<p className="text-sm text-muted-foreground line-clamp-1">
|
|
|
|
|
{program.description}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>{program.year}</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<div>
|
|
|
|
|
<p>{program._count.rounds} total</p>
|
|
|
|
|
{program.rounds.length > 0 && (
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
{program.rounds.length} active
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<Badge variant={statusColors[program.status] || 'secondary'}>
|
|
|
|
|
{program.status}
|
|
|
|
|
</Badge>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>{formatDateOnly(program.createdAt)}</TableCell>
|
|
|
|
|
<TableCell className="text-right">
|
|
|
|
|
<DropdownMenu>
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
<Button variant="ghost" size="icon">
|
|
|
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
|
|
|
<span className="sr-only">Actions</span>
|
|
|
|
|
</Button>
|
|
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
<DropdownMenuContent align="end">
|
|
|
|
|
<DropdownMenuItem asChild>
|
|
|
|
|
<Link href={`/admin/programs/${program.id}`}>
|
|
|
|
|
<Eye className="mr-2 h-4 w-4" />
|
|
|
|
|
View Details
|
|
|
|
|
</Link>
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
<DropdownMenuItem asChild>
|
|
|
|
|
<Link href={`/admin/programs/${program.id}/edit`}>
|
|
|
|
|
<Pencil className="mr-2 h-4 w-4" />
|
|
|
|
|
Edit
|
|
|
|
|
</Link>
|
|
|
|
|
</DropdownMenuItem>
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
<DropdownMenuItem asChild>
|
Fix build errors: add missing Prisma models/fields and resolve TypeScript type errors
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>
2026-02-08 14:04:02 +01:00
|
|
|
<Link href={`/admin/programs/${program.id}/apply-settings` as Route}>
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
<Wand2 className="mr-2 h-4 w-4" />
|
|
|
|
|
Apply Settings
|
|
|
|
|
</Link>
|
|
|
|
|
</DropdownMenuItem>
|
2026-01-30 13:41:32 +01:00
|
|
|
</DropdownMenuContent>
|
|
|
|
|
</DropdownMenu>
|
|
|
|
|
</TableCell>
|
|
|
|
|
</TableRow>
|
|
|
|
|
))}
|
|
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* Mobile card view */}
|
|
|
|
|
<div className="space-y-4 md:hidden">
|
|
|
|
|
{programs.map((program) => (
|
|
|
|
|
<Card key={program.id}>
|
|
|
|
|
<CardHeader className="pb-3">
|
|
|
|
|
<div className="flex items-start justify-between">
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<CardTitle className="text-base">{program.name}</CardTitle>
|
|
|
|
|
<CardDescription>{program.year}</CardDescription>
|
|
|
|
|
</div>
|
|
|
|
|
<Badge variant={statusColors[program.status] || 'secondary'}>
|
|
|
|
|
{program.status}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-3">
|
|
|
|
|
<div className="flex items-center justify-between text-sm">
|
|
|
|
|
<span className="text-muted-foreground">Rounds</span>
|
|
|
|
|
<span>
|
|
|
|
|
{program._count.rounds} ({program.rounds.length} active)
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center justify-between text-sm">
|
|
|
|
|
<span className="text-muted-foreground">Created</span>
|
|
|
|
|
<span>{formatDateOnly(program.createdAt)}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex gap-2 pt-2">
|
|
|
|
|
<Button variant="outline" size="sm" className="flex-1" asChild>
|
|
|
|
|
<Link href={`/admin/programs/${program.id}`}>
|
|
|
|
|
<Eye className="mr-2 h-4 w-4" />
|
|
|
|
|
View
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
<Button variant="outline" size="sm" className="flex-1" asChild>
|
|
|
|
|
<Link href={`/admin/programs/${program.id}/edit`}>
|
|
|
|
|
<Pencil className="mr-2 h-4 w-4" />
|
|
|
|
|
Edit
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
<Button variant="outline" size="sm" className="flex-1" asChild>
|
Fix build errors: add missing Prisma models/fields and resolve TypeScript type errors
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>
2026-02-08 14:04:02 +01:00
|
|
|
<Link href={`/admin/programs/${program.id}/apply-settings` as Route}>
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
<Wand2 className="mr-2 h-4 w-4" />
|
|
|
|
|
Apply
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
2026-01-30 13:41:32 +01:00
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ProgramsSkeleton() {
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardContent className="p-6">
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
{[...Array(5)].map((_, i) => (
|
|
|
|
|
<div key={i} className="flex items-center justify-between">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Skeleton className="h-5 w-48" />
|
|
|
|
|
<Skeleton className="h-4 w-32" />
|
|
|
|
|
</div>
|
|
|
|
|
<Skeleton className="h-9 w-9" />
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function ProgramsPage() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-2xl font-semibold tracking-tight">Programs</h1>
|
|
|
|
|
<p className="text-muted-foreground">
|
|
|
|
|
Manage your ocean protection programs
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<Button asChild>
|
|
|
|
|
<Link href="/admin/programs/new">
|
|
|
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
|
|
|
New Program
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Content */}
|
|
|
|
|
<Suspense fallback={<ProgramsSkeleton />}>
|
|
|
|
|
<ProgramsContent />
|
|
|
|
|
</Suspense>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|