diff --git a/src/app/(admin)/admin/rounds/page.tsx b/src/app/(admin)/admin/rounds/page.tsx
index 8257d99..a041c6c 100644
--- a/src/app/(admin)/admin/rounds/page.tsx
+++ b/src/app/(admin)/admin/rounds/page.tsx
@@ -1,8 +1,9 @@
'use client'
-import { Suspense } from 'react'
+import { Suspense, useState } from 'react'
import Link from 'next/link'
import { trpc } from '@/lib/trpc/client'
+import { toast } from 'sonner'
import {
Card,
CardContent,
@@ -28,6 +29,16 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from '@/components/ui/alert-dialog'
import {
Plus,
MoreHorizontal,
@@ -39,6 +50,8 @@ import {
CheckCircle2,
Clock,
Archive,
+ Trash2,
+ Loader2,
} from 'lucide-react'
import { format, isPast, isFuture } from 'date-fns'
@@ -122,6 +135,7 @@ function RoundsContent() {
function RoundRow({ round }: { round: any }) {
const utils = trpc.useUtils()
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const updateStatus = trpc.round.updateStatus.useMutation({
onSuccess: () => {
@@ -129,6 +143,16 @@ function RoundRow({ round }: { round: any }) {
},
})
+ const deleteRound = trpc.round.delete.useMutation({
+ onSuccess: () => {
+ toast.success('Round deleted successfully')
+ utils.program.list.invalidate()
+ },
+ onError: (error) => {
+ toast.error(error.message || 'Failed to delete round')
+ },
+ })
+
const getStatusBadge = () => {
const now = new Date()
const isVotingOpen =
@@ -284,8 +308,42 @@ function RoundRow({ round }: { round: any }) {
Archive Round
)}
+
+ setShowDeleteDialog(true)}
+ >
+
+ Delete Round
+
+
+
+
+
+ Delete Round
+
+ Are you sure you want to delete "{round.name}"? This will
+ permanently delete all {round._count?.projects || 0} projects,{' '}
+ {round._count?.assignments || 0} assignments, and all evaluations
+ in this round. This action cannot be undone.
+
+
+
+ Cancel
+ deleteRound.mutate({ id: round.id })}
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
+ >
+ {deleteRound.isPending ? (
+
+ ) : null}
+ Delete
+
+
+
+
)
diff --git a/src/server/routers/round.ts b/src/server/routers/round.ts
index aaf3396..2e1e752 100644
--- a/src/server/routers/round.ts
+++ b/src/server/routers/round.ts
@@ -341,6 +341,45 @@ export const roundRouter = router({
})
}),
+ /**
+ * Delete a round (admin only)
+ * Cascades to projects, assignments, evaluations, etc.
+ */
+ delete: adminProcedure
+ .input(z.object({ id: z.string() }))
+ .mutation(async ({ ctx, input }) => {
+ const round = await ctx.prisma.round.findUniqueOrThrow({
+ where: { id: input.id },
+ include: {
+ _count: { select: { projects: true, assignments: true } },
+ },
+ })
+
+ await ctx.prisma.round.delete({
+ where: { id: input.id },
+ })
+
+ // Audit log
+ await ctx.prisma.auditLog.create({
+ data: {
+ userId: ctx.user.id,
+ action: 'DELETE',
+ entityType: 'Round',
+ entityId: input.id,
+ detailsJson: {
+ name: round.name,
+ status: round.status,
+ projectsDeleted: round._count.projects,
+ assignmentsDeleted: round._count.assignments,
+ },
+ ipAddress: ctx.ip,
+ userAgent: ctx.userAgent,
+ },
+ })
+
+ return round
+ }),
+
/**
* Check if a round has any submitted evaluations
*/