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'
|
'use client'
|
||||||
|
|
||||||
import { Suspense } from 'react'
|
import { Suspense, useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { trpc } from '@/lib/trpc/client'
|
import { trpc } from '@/lib/trpc/client'
|
||||||
|
import { toast } from 'sonner'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
@ -28,6 +29,16 @@ import {
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog'
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
|
|
@ -39,6 +50,8 @@ import {
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Clock,
|
Clock,
|
||||||
Archive,
|
Archive,
|
||||||
|
Trash2,
|
||||||
|
Loader2,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { format, isPast, isFuture } from 'date-fns'
|
import { format, isPast, isFuture } from 'date-fns'
|
||||||
|
|
||||||
|
|
@ -122,6 +135,7 @@ function RoundsContent() {
|
||||||
|
|
||||||
function RoundRow({ round }: { round: any }) {
|
function RoundRow({ round }: { round: any }) {
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
|
||||||
|
|
||||||
const updateStatus = trpc.round.updateStatus.useMutation({
|
const updateStatus = trpc.round.updateStatus.useMutation({
|
||||||
onSuccess: () => {
|
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 getStatusBadge = () => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const isVotingOpen =
|
const isVotingOpen =
|
||||||
|
|
@ -284,8 +308,42 @@ function RoundRow({ round }: { round: any }) {
|
||||||
Archive Round
|
Archive Round
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="text-destructive focus:text-destructive"
|
||||||
|
onClick={() => setShowDeleteDialog(true)}
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
Delete Round
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</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>
|
</TableCell>
|
||||||
</TableRow>
|
</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
|
* Check if a round has any submitted evaluations
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue