From de4638bc46973f355a457938ef5ae1177e4c750a Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Thu, 11 Apr 2024 10:22:02 +0100 Subject: [PATCH] - ENCOA-3: Added the ability to delete multiple codes at once; - ENCOA-5 Added a column for the Creator on the code list; --- src/components/Low/Button.tsx | 2 +- src/pages/(admin)/Lists/CodeList.tsx | 127 ++++++++++++++++++++++----- src/pages/api/code/index.ts | 21 ++++- 3 files changed, 124 insertions(+), 26 deletions(-) diff --git a/src/components/Low/Button.tsx b/src/components/Low/Button.tsx index 0adcd018..99de3d4b 100644 --- a/src/components/Low/Button.tsx +++ b/src/components/Low/Button.tsx @@ -62,8 +62,8 @@ export default function Button({ onClick={onClick} className={clsx( "py-4 px-6 rounded-full transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer", - className, colorClassNames[color][variant], + className, )} disabled={disabled || isLoading}> {!isLoading && children} diff --git a/src/pages/(admin)/Lists/CodeList.tsx b/src/pages/(admin)/Lists/CodeList.tsx index c5c0d548..88a513df 100644 --- a/src/pages/(admin)/Lists/CodeList.tsx +++ b/src/pages/(admin)/Lists/CodeList.tsx @@ -1,15 +1,69 @@ +import Button from "@/components/Low/Button"; +import Checkbox from "@/components/Low/Checkbox"; import useCodes from "@/hooks/useCodes"; +import useUser from "@/hooks/useUser"; +import useUsers from "@/hooks/useUsers"; import {Code, User} from "@/interfaces/user"; +import {USER_TYPE_LABELS} from "@/resources/user"; import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; import axios from "axios"; +import {useEffect, useState} from "react"; import {BsTrash} from "react-icons/bs"; import {toast} from "react-toastify"; const columnHelper = createColumnHelper(); +const CreatorCell = ({id, users}: {id: string; users: User[]}) => { + const [creatorUser, setCreatorUser] = useState(); + + useEffect(() => { + console.log(id); + setCreatorUser(users.find((x) => x.id === id)); + }, [id, users]); + + return ( + <> + {(creatorUser?.type === "corporate" ? creatorUser?.corporateInformation?.companyInformation?.name : creatorUser?.name || "N/A") || "N/A"}{" "} + {creatorUser && `(${USER_TYPE_LABELS[creatorUser.type]})`} + + ); +}; + export default function CodeList({user}: {user: User}) { + const [selectedCodes, setSelectedCodes] = useState([]); + + const {users} = useUsers(); const {codes, reload} = useCodes(user?.type === "corporate" ? user?.id : undefined); + const toggleCode = (id: string) => { + setSelectedCodes((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])); + }; + + const deleteCodes = async (codes: string[]) => { + if (!confirm(`Are you sure you want to delete these ${codes.length} code(s)?`)) return; + + const params = new URLSearchParams(); + codes.forEach((code) => params.append("code", code)); + + axios + .delete(`/api/code?${params.toString()}`) + .then(() => toast.success(`Deleted the codes!`)) + .catch((reason) => { + if (reason.response.status === 404) { + toast.error("Code not found!"); + return; + } + + if (reason.response.status === 403) { + toast.error("You do not have permission to delete this code!"); + return; + } + + toast.error("Something went wrong, please try again later."); + }) + .finally(reload); + }; + const deleteCode = async (code: Code) => { if (!confirm(`Are you sure you want to delete this "${code.code}" code?`)) return; @@ -33,6 +87,14 @@ export default function CodeList({user}: {user: User}) { }; const defaultColumns = [ + columnHelper.accessor("code", { + header: "", + cell: (info) => ( + toggleCode(info.getValue())}> + {""} + + ), + }), columnHelper.accessor("code", { header: "Code", cell: (info) => info.getValue(), @@ -41,6 +103,10 @@ export default function CodeList({user}: {user: User}) { header: "Invited E-mail", cell: (info) => info.getValue() || "N/A", }), + columnHelper.accessor("creator", { + header: "Creator", + cell: (info) => , + }), columnHelper.accessor("userId", { header: "Availability", cell: (info) => @@ -78,29 +144,42 @@ export default function CodeList({user}: {user: User}) { }); return ( - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} - -
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
+ <> +
+ {selectedCodes.length} code(s) selected + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+ ); } diff --git a/src/pages/api/code/index.ts b/src/pages/api/code/index.ts index 680786fc..5b36b61c 100644 --- a/src/pages/api/code/index.ts +++ b/src/pages/api/code/index.ts @@ -1,7 +1,7 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type {NextApiRequest, NextApiResponse} from "next"; import {app} from "@/firebase"; -import {getFirestore, setDoc, doc, query, collection, where, getDocs} from "firebase/firestore"; +import {getFirestore, setDoc, doc, query, collection, where, getDocs, getDoc, deleteDoc} from "firebase/firestore"; import {withIronSessionApiRoute} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; import {Type} from "@/interfaces/user"; @@ -16,6 +16,7 @@ export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "GET") return get(req, res); if (req.method === "POST") return post(req, res); + if (req.method === "DELETE") return del(req, res); return res.status(404).json({ok: false}); } @@ -121,3 +122,21 @@ async function post(req: NextApiRequest, res: NextApiResponse) { res.status(200).json({ok: true, valid: results.filter((x) => x).length}); }); } + +async function del(req: NextApiRequest, res: NextApiResponse) { + if (!req.session.user) { + res.status(401).json({ok: false, reason: "You must be logged in to generate a code!"}); + return; + } + + const codes = req.query.code as string[]; + + for (const code of codes) { + const snapshot = await getDoc(doc(db, "codes", code as string)); + if (!snapshot.exists()) continue; + + await deleteDoc(snapshot.ref); + } + + res.status(200).json({codes}); +}