import Button from "@/components/Low/Button"; import Checkbox from "@/components/Low/Checkbox"; import Select from "@/components/Low/Select"; 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 moment from "moment"; import { useEffect, useState, useMemo } from "react"; import { BsTrash } from "react-icons/bs"; import { toast } from "react-toastify"; import ReactDatePicker from "react-datepicker"; import clsx from "clsx"; import { checkAccess } from "@/utils/permissions"; import usePermissions from "@/hooks/usePermissions"; const columnHelper = createColumnHelper(); const CreatorCell = ({ id, users }: { id: string; users: User[] }) => { const [creatorUser, setCreatorUser] = useState(); useEffect(() => { 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, canDeleteCodes }: { user: User, canDeleteCodes?: boolean }) { const [selectedCodes, setSelectedCodes] = useState([]); const [filteredCorporate, setFilteredCorporate] = useState(user?.type === "corporate" ? user : undefined); const [filterAvailability, setFilterAvailability] = useState<"in-use" | "unused">(); const { permissions } = usePermissions(user?.id || ""); const { users } = useUsers(); const { codes, reload } = useCodes(user?.type === "corporate" ? user?.id : undefined); const [startDate, setStartDate] = useState(moment("01/01/2023").toDate()); const [endDate, setEndDate] = useState(moment().endOf("day").toDate()); const filteredCodes = useMemo(() => { return codes.filter((x) => { // TODO: if the expiry date is missing, it does not make sense to filter by date // so we need to find a way to handle this edge case if (startDate && endDate && x.expiryDate) { const date = moment(x.expiryDate); if (date.isBefore(startDate) || date.isAfter(endDate)) { return false; } } if (filteredCorporate && x.creator !== filteredCorporate.id) return false; if (filterAvailability) { if (filterAvailability === "in-use" && !x.userId) return false; if (filterAvailability === "unused" && x.userId) return false; } return true; }); }, [codes, startDate, endDate, filteredCorporate, filterAvailability]); const toggleCode = (id: string) => { setSelectedCodes((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])); }; const toggleAllCodes = (checked: boolean) => { if (checked) return setSelectedCodes(filteredCodes.filter((x) => !x.userId).map((x) => x.code)); return setSelectedCodes([]); }; const deleteCodes = async (codes: string[]) => { if (!canDeleteCodes) return 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!`); setSelectedCodes([]); }) .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 (!canDeleteCodes) return if (!confirm(`Are you sure you want to delete this "${code.code}" code?`)) return; axios .delete(`/api/code/${code.code}`) .then(() => toast.success(`Deleted the "${code.code}" exam`)) .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 defaultColumns = [ columnHelper.accessor("code", { id: "codeCheckbox", header: () => ( !x.userId).length === 0} isChecked={ selectedCodes.length === filteredCodes.filter((x) => !x.userId).length && filteredCodes.filter((x) => !x.userId).length > 0 } onChange={(checked) => toggleAllCodes(checked)}> {""} ), cell: (info) => !info.row.original.userId ? ( toggleCode(info.getValue())}> {""} ) : null, }), columnHelper.accessor("code", { header: "Code", cell: (info) => info.getValue(), }), columnHelper.accessor("creationDate", { header: "Creation Date", cell: (info) => (info.getValue() ? moment(info.getValue()).format("DD/MM/YYYY") : "N/A"), }), columnHelper.accessor("email", { header: "Invited E-mail", cell: (info) => info.getValue() || "N/A", }), columnHelper.accessor("creator", { header: "Creator", cell: (info) => , }), columnHelper.accessor("userId", { header: "Availability", cell: (info) => info.getValue() ? (
In Use ) : (
Unused ), }), { header: "", id: "actions", cell: ({ row }: { row: { original: Code } }) => { return (
{canDeleteCodes && !row.original.userId && (
deleteCode(row.original)}>
)}
); }, }, ]; const table = useReactTable({ data: filteredCodes, columns: defaultColumns, getCoreRowModel: getCoreRowModel(), }); return ( <>
setFilterAvailability(value ? (value.value as typeof filterAvailability) : undefined)} /> moment(date).isSameOrBefore(moment(new Date()))} onChange={([initialDate, finalDate]: [Date, Date]) => { setStartDate(initialDate ?? moment("01/01/2023").toDate()); if (finalDate) { // basicly selecting a final day works as if I'm selecting the first // minute of that day. this way it covers the whole day setEndDate(moment(finalDate).endOf("day").toDate()); return; } setEndDate(null); }} />
{canDeleteCodes && (
{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())}
); }