Add round delete with confirmation dialog
Build and Push Docker Image / build (push) Successful in 7m57s
Details
Build and Push Docker Image / build (push) Successful in 7m57s
Details
- Add delete procedure to round tRPC router with cascade and audit log - Add delete option to rounds list dropdown menu - Show confirmation dialog with project/assignment counts before deletion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4c5a49cede
commit
0c0a9b7eb5
|
|
@ -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
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete Round
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Round</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
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.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => deleteRound.mutate({ id: round.id })}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{deleteRound.isPending ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : null}
|
||||
Delete
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue