From 2fefa680f1682d624fc7373966a619fb370dc715 Mon Sep 17 00:00:00 2001 From: atrincas Date: Mon, 23 Dec 2024 17:49:15 +0100 Subject: [PATCH] Delete custom project implementation --- client/src/containers/my-projects/columns.tsx | 96 +++++++++++++++++-- shared/contracts/custom-projects.contract.ts | 8 ++ 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/client/src/containers/my-projects/columns.tsx b/client/src/containers/my-projects/columns.tsx index cb4e01d9..c4029779 100644 --- a/client/src/containers/my-projects/columns.tsx +++ b/client/src/containers/my-projects/columns.tsx @@ -1,3 +1,5 @@ +import { useCallback } from "react"; + import Link from "next/link"; import { @@ -8,9 +10,11 @@ import { import { ACTIVITY } from "@shared/entities/activity.enum"; import { CustomProject as CustomProjectEntity } from "@shared/entities/custom-project.entity"; import { Table as TableInstance, Row, ColumnDef } from "@tanstack/react-table"; +import { useSession } from "next-auth/react"; import { formatCurrency } from "@/lib/format"; -import { cn } from "@/lib/utils"; +import { client } from "@/lib/query-client"; +import { cn, getAuthHeader } from "@/lib/utils"; import { useFeatureFlags } from "@/hooks/use-feature-flags"; @@ -23,6 +27,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useToast } from "@/components/ui/toast/use-toast"; type CustomProject = Partial; @@ -30,8 +35,77 @@ type CustomColumn = ColumnDef & { className?: string; }; -const ActionsDropdown = () => { +const ActionsDropdown = ({ + instance, +}: { + instance: TableInstance | Row; +}) => { const { "update-selection": updateSelection } = useFeatureFlags(); + const isHeader = "getSelectedRowModel" in instance; + const deleteLabel = isHeader ? "Delete selection" : "Delete project"; + const { data: session } = useSession(); + const { toast } = useToast(); + const deleteCustomProject = useCallback( + async (id: string): Promise => { + try { + const { status } = + await client.customProjects.deleteCustomProject.mutation({ + params: { + id, + }, + extraHeaders: { + ...getAuthHeader(session?.accessToken as string), + }, + }); + + return status === 200; + } catch (e) { + return false; + } + }, + [session?.accessToken], + ); + + const handleDelete = async () => { + const results: boolean[] = []; + + if (isHeader) { + const selectedRows = ( + instance as TableInstance + ).getSelectedRowModel().rows; + + for (const row of selectedRows) { + const result = await deleteCustomProject(row.original.id as string); + results.push(result); + } + } else if (instance.original.id) { + const result = await deleteCustomProject(instance.original.id); + results.push(result); + } + + const successCount = results.filter(Boolean).length; + const failureCount = results.length - successCount; + + if (successCount > 0) { + toast({ + description: + successCount === 1 + ? "Project deleted successfully" + : `${successCount} projects deleted successfully`, + }); + } + + if (failureCount > 0) { + toast({ + variant: "destructive", + description: + failureCount === 1 + ? "Failed to delete project" + : `Failed to delete ${failureCount} projects`, + }); + } + }; + return (
@@ -47,9 +121,17 @@ const ActionsDropdown = () => { Update selection )} - - - Delete selection + ).getSelectedRowModel() + .rows.length === 0 + } + onClick={handleDelete} + > + + {deleteLabel} @@ -125,8 +207,8 @@ export const columns: CustomColumn[] = [ }, { accessorKey: "actions", - header: ActionsDropdown, - cell: ActionsDropdown, + header: ({ table }) => , + cell: ({ row }) => , className: "!border-l-0", enableSorting: false, }, diff --git a/shared/contracts/custom-projects.contract.ts b/shared/contracts/custom-projects.contract.ts index 6cc56d43..b01d1b8e 100644 --- a/shared/contracts/custom-projects.contract.ts +++ b/shared/contracts/custom-projects.contract.ts @@ -96,6 +96,14 @@ export const customProjectContract = contract.router({ }, body: contract.type(), }, + deleteCustomProject: { + method: "DELETE", + path: "/custom-projects/:id", + responses: { + 200: contract.type(), + }, + body: null, + }, }); // TODO: Due to dificulties crafting a deeply nested conditional schema, I will go forward with nestjs custom validation pipe for now