- ENCOA-3: Added the ability to delete multiple codes at once;

- ENCOA-5 Added a column for the Creator on the code list;
This commit is contained in:
Tiago Ribeiro
2024-04-11 10:22:02 +01:00
parent c9740fe8ee
commit de4638bc46
3 changed files with 124 additions and 26 deletions

View File

@@ -62,8 +62,8 @@ export default function Button({
onClick={onClick} onClick={onClick}
className={clsx( className={clsx(
"py-4 px-6 rounded-full transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer", "py-4 px-6 rounded-full transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer",
className,
colorClassNames[color][variant], colorClassNames[color][variant],
className,
)} )}
disabled={disabled || isLoading}> disabled={disabled || isLoading}>
{!isLoading && children} {!isLoading && children}

View File

@@ -1,15 +1,69 @@
import Button from "@/components/Low/Button";
import Checkbox from "@/components/Low/Checkbox";
import useCodes from "@/hooks/useCodes"; import useCodes from "@/hooks/useCodes";
import useUser from "@/hooks/useUser";
import useUsers from "@/hooks/useUsers";
import {Code, User} from "@/interfaces/user"; import {Code, User} from "@/interfaces/user";
import {USER_TYPE_LABELS} from "@/resources/user";
import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
import axios from "axios"; import axios from "axios";
import {useEffect, useState} from "react";
import {BsTrash} from "react-icons/bs"; import {BsTrash} from "react-icons/bs";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
const columnHelper = createColumnHelper<Code>(); const columnHelper = createColumnHelper<Code>();
const CreatorCell = ({id, users}: {id: string; users: User[]}) => {
const [creatorUser, setCreatorUser] = useState<User>();
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}) { export default function CodeList({user}: {user: User}) {
const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
const {users} = useUsers();
const {codes, reload} = useCodes(user?.type === "corporate" ? user?.id : undefined); 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) => { const deleteCode = async (code: Code) => {
if (!confirm(`Are you sure you want to delete this "${code.code}" code?`)) return; 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 = [ const defaultColumns = [
columnHelper.accessor("code", {
header: "",
cell: (info) => (
<Checkbox isChecked={selectedCodes.includes(info.getValue())} onChange={() => toggleCode(info.getValue())}>
{""}
</Checkbox>
),
}),
columnHelper.accessor("code", { columnHelper.accessor("code", {
header: "Code", header: "Code",
cell: (info) => info.getValue(), cell: (info) => info.getValue(),
@@ -41,6 +103,10 @@ export default function CodeList({user}: {user: User}) {
header: "Invited E-mail", header: "Invited E-mail",
cell: (info) => info.getValue() || "N/A", cell: (info) => info.getValue() || "N/A",
}), }),
columnHelper.accessor("creator", {
header: "Creator",
cell: (info) => <CreatorCell id={info.getValue()} users={users} />,
}),
columnHelper.accessor("userId", { columnHelper.accessor("userId", {
header: "Availability", header: "Availability",
cell: (info) => cell: (info) =>
@@ -78,6 +144,18 @@ export default function CodeList({user}: {user: User}) {
}); });
return ( return (
<>
<div className="flex items-center justify-between pb-4 pt-1 px-4">
<span>{selectedCodes.length} code(s) selected</span>
<Button
disabled={selectedCodes.length === 0}
variant="outline"
color="red"
className="!py-1 px-10"
onClick={() => deleteCodes(selectedCodes)}>
Delete
</Button>
</div>
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full"> <table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
<thead> <thead>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
@@ -102,5 +180,6 @@ export default function CodeList({user}: {user: User}) {
))} ))}
</tbody> </tbody>
</table> </table>
</>
); );
} }

View File

@@ -1,7 +1,7 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type {NextApiRequest, NextApiResponse} from "next"; import type {NextApiRequest, NextApiResponse} from "next";
import {app} from "@/firebase"; 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 {withIronSessionApiRoute} from "iron-session/next";
import {sessionOptions} from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import {Type} from "@/interfaces/user"; import {Type} from "@/interfaces/user";
@@ -16,6 +16,7 @@ export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) { async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") return get(req, res); if (req.method === "GET") return get(req, res);
if (req.method === "POST") return post(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}); 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}); 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});
}