From c9f153c345d0c4510e251fb1c532dcc283fa9a50 Mon Sep 17 00:00:00 2001 From: Guillermo Pages Date: Sat, 6 Dec 2025 22:39:03 +0100 Subject: [PATCH] Add DELETE endpoint for competitions (draft/cancelled only) --- .../CompetitionDetailComponent.tsx | 73 ++++++++++++++++++- .../mutations/useCompetitionMutations.ts | 25 +++++++ src/lib/api/competition-admin.ts | 19 +++++ 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/admin/facilities/[facility_id]/competitions/[competition_id]/CompetitionDetailComponent.tsx b/src/app/[locale]/admin/facilities/[facility_id]/competitions/[competition_id]/CompetitionDetailComponent.tsx index 22db764..d416da3 100644 --- a/src/app/[locale]/admin/facilities/[facility_id]/competitions/[competition_id]/CompetitionDetailComponent.tsx +++ b/src/app/[locale]/admin/facilities/[facility_id]/competitions/[competition_id]/CompetitionDetailComponent.tsx @@ -18,6 +18,7 @@ import { ListOrdered, FileText, CalendarClock, + Trash2, } from 'lucide-react'; import useTranslation from '@/src/hooks/useTranslation'; import Card from '@/src/components/cards/Card'; @@ -28,7 +29,9 @@ import { useStartCompetition, useFinishCompetition, useCancelCompetition, + useDeleteCompetition, } from '@/src/hooks/mutations/useCompetitionMutations'; +import { useRouter } from 'next/navigation'; import OverviewTab from './tabs/OverviewTab'; import RegistrationsTab from './tabs/RegistrationsTab'; import ParticipantsTab from './tabs/ParticipantsTab'; @@ -71,20 +74,76 @@ function formatDate(dateString: string): string { function StatusTransitionButton({ status, competitionId, + facilityId, + locale, }: { status: CompetitionStatus; competitionId: number; + facilityId: number; + locale: string; }) { + const router = useRouter(); const publishMutation = usePublishCompetition(competitionId); const startMutation = useStartCompetition(competitionId); const finishMutation = useFinishCompetition(competitionId); const cancelMutation = useCancelCompetition(competitionId); + const deleteMutation = useDeleteCompetition(competitionId); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const isPending = publishMutation.isPending || startMutation.isPending || finishMutation.isPending || - cancelMutation.isPending; + cancelMutation.isPending || + deleteMutation.isPending; + + const canDelete = status === 'draft' || status === 'cancelled'; + + const handleDelete = () => { + deleteMutation.mutate(undefined, { + onSuccess: () => { + router.push(`/${locale}/admin/facilities/${facilityId}/competitions`); + }, + }); + }; + + const DeleteButton = () => ( + <> + {showDeleteConfirm ? ( +
+ Delete permanently? + + +
+ ) : ( + + )} + + ); if (status === 'draft') { return ( @@ -101,6 +160,7 @@ function StatusTransitionButton({ )} Publish + ); } @@ -155,6 +215,15 @@ function StatusTransitionButton({ ); } + // For cancelled status, show delete button + if (canDelete) { + return ( +
+ +
+ ); + } + return null; } @@ -234,7 +303,7 @@ export default function CompetitionDetailComponent({ Save as Template - + diff --git a/src/hooks/mutations/useCompetitionMutations.ts b/src/hooks/mutations/useCompetitionMutations.ts index 27310d5..d51f020 100644 --- a/src/hooks/mutations/useCompetitionMutations.ts +++ b/src/hooks/mutations/useCompetitionMutations.ts @@ -8,6 +8,7 @@ import { startCompetition, finishCompetition, cancelCompetition, + deleteCompetition, saveCompetitionAsTemplate, approveRegistration, rejectRegistration, @@ -237,6 +238,30 @@ export function useCancelCompetition(competitionId: number) { }); } +/** + * Delete a competition (draft or cancelled only) + */ +export function useDeleteCompetition(competitionId: number) { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async () => { + const result = await deleteCompetition(competitionId); + if (!result.success) { + throw new Error(result.error.detail); + } + return result.data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: competitionQueryKeys.lists() }); + // Remove the detail query since the competition no longer exists + queryClient.removeQueries({ + queryKey: competitionQueryKeys.detail(competitionId), + }); + }, + }); +} + /** * Save competition as template */ diff --git a/src/lib/api/competition-admin.ts b/src/lib/api/competition-admin.ts index 5bd2471..d08f5d6 100644 --- a/src/lib/api/competition-admin.ts +++ b/src/lib/api/competition-admin.ts @@ -412,6 +412,25 @@ export async function cancelCompetition( } } +/** + * DELETE /competitions/{competition_id} + * Permanently delete a competition (draft or cancelled only) + */ +export async function deleteCompetition( + competitionId: number +): Promise> { + try { + const response = await apiFetch(`/competitions/${competitionId}`, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + }); + + return handleApiResponse(response); + } catch (error) { + return buildNetworkError(error instanceof Error ? error.message : 'Failed to delete competition'); + } +} + /** * POST /competitions/{competition_id}/save-as-template * Create a template from an existing competition's config