ENCOA-114: In Exam List and Group List provide Fuzzy Search Filter

This commit is contained in:
Tiago Ribeiro
2024-08-27 10:53:50 +01:00
parent 82233c7d53
commit cd85c71aec
3 changed files with 172 additions and 212 deletions

View File

@@ -8,18 +8,16 @@ import { Type, User } from "@/interfaces/user";
import useExamStore from "@/stores/examStore";
import {getExamById} from "@/utils/exams";
import {countExercises} from "@/utils/moduleUtils";
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
import axios from "axios";
import clsx from "clsx";
import {capitalize} from "lodash";
import {useRouter} from "next/router";
import {BsCheck, BsTrash, BsUpload} from "react-icons/bs";
import {toast} from "react-toastify";
import {useListSearch} from "@/hooks/useListSearch";
const searchFields = [["module"], ["id"], ["createdBy"]];
const CLASSES: {[key in Module]: string} = {
reading: "text-ielts-reading",
@@ -51,6 +49,8 @@ export default function ExamList({ user }: { user: User }) {
});
}, [exams, users]);
const {rows: filteredRows, renderSearch} = useListSearch<Exam>(searchFields, parsedExams);
const setExams = useExamStore((state) => state.setExams);
const setSelectedModules = useExamStore((state) => state.setSelectedModules);
@@ -59,12 +59,9 @@ export default function ExamList({ user }: { user: User }) {
const loadExam = async (module: Module, examId: string) => {
const exam = await getExamById(module, examId.trim());
if (!exam) {
toast.error(
"Unknown Exam ID! Please make sure you selected the right module and entered the right exam ID",
{
toast.error("Unknown Exam ID! Please make sure you selected the right module and entered the right exam ID", {
toastId: "invalid-exam-id",
}
);
});
return;
}
@@ -76,12 +73,7 @@ export default function ExamList({ user }: { user: User }) {
};
const deleteExam = async (exam: Exam) => {
if (
!confirm(
`Are you sure you want to delete this ${capitalize(exam.module)} exam?`
)
)
return;
if (!confirm(`Are you sure you want to delete this ${capitalize(exam.module)} exam?`)) return;
axios
.delete(`/api/exam/${exam.module}/${exam.id}`)
@@ -103,11 +95,7 @@ export default function ExamList({ user }: { user: User }) {
};
const getTotalExercises = (exam: Exam) => {
if (
exam.module === "reading" ||
exam.module === "listening" ||
exam.module === "level"
) {
if (exam.module === "reading" || exam.module === "listening" || exam.module === "level") {
return countExercises(exam.parts.flatMap((x) => x.exercises));
}
@@ -121,11 +109,7 @@ export default function ExamList({ user }: { user: User }) {
}),
columnHelper.accessor("module", {
header: "Module",
cell: (info) => (
<span className={CLASSES[info.getValue()]}>
{capitalize(info.getValue())}
</span>
),
cell: (info) => <span className={CLASSES[info.getValue()]}>{capitalize(info.getValue())}</span>,
}),
columnHelper.accessor((x) => getTotalExercises(x), {
header: "Exercises",
@@ -159,18 +143,11 @@ export default function ExamList({ user }: { user: User }) {
<div
data-tip="Load exam"
className="cursor-pointer tooltip"
onClick={async () =>
await loadExam(row.original.module, row.original.id)
}
>
onClick={async () => await loadExam(row.original.module, row.original.id)}>
<BsUpload className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</div>
{PERMISSIONS.examManagement.delete.includes(user.type) && (
<div
data-tip="Delete"
className="cursor-pointer tooltip"
onClick={() => deleteExam(row.original)}
>
<div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deleteExam(row.original)}>
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</div>
)}
@@ -181,24 +158,21 @@ export default function ExamList({ user }: { user: User }) {
];
const table = useReactTable({
data: parsedExams,
data: filteredRows,
columns: defaultColumns,
getCoreRowModel: getCoreRowModel(),
});
return (
<div className="flex flex-col gap-4 w-full h-full">
{renderSearch()}
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th className="p-4 text-left" key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</th>
))}
</tr>
@@ -206,10 +180,7 @@ export default function ExamList({ user }: { user: User }) {
</thead>
<tbody className="px-2">
{table.getRowModel().rows.map((row) => (
<tr
className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2"
key={row.id}
>
<tr className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2" key={row.id}>
{row.getVisibleCells().map((cell) => (
<td className="px-4 py-2" key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
@@ -219,5 +190,6 @@ export default function ExamList({ user }: { user: User }) {
))}
</tbody>
</table>
</div>
);
}

View File

@@ -17,6 +17,8 @@ import {getUserCorporate} from "@/utils/groups";
import {isAgentUser, isCorporateUser, USER_TYPE_LABELS} from "@/resources/user";
import {checkAccess} from "@/utils/permissions";
import usePermissions from "@/hooks/usePermissions";
import {useListSearch} from "@/hooks/useListSearch";
const searchFields = [["name"]];
const columnHelper = createColumnHelper<Group>();
const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/);
@@ -217,6 +219,8 @@ export default function GroupList({user}: {user: User}) {
adminAdmins: user?.id,
});
const {rows: filteredRows, renderSearch} = useListSearch<Group>(searchFields, groups);
const deleteGroup = (group: Group) => {
if (!confirm(`Are you sure you want to delete "${group.name}"?`)) return;
@@ -283,7 +287,7 @@ export default function GroupList({user}: {user: User}) {
];
const table = useReactTable({
data: groups,
data: filteredRows,
columns: defaultColumns,
getCoreRowModel: getCoreRowModel(),
});
@@ -295,7 +299,7 @@ export default function GroupList({user}: {user: User}) {
};
return (
<div className="h-full w-full rounded-xl">
<div className="h-full w-full rounded-xl flex flex-col gap-4">
<Modal isOpen={isCreating || !!editingGroup} onClose={closeModal} title={editingGroup ? `Editing ${editingGroup.name}` : "New Group"}>
<CreatePanel
group={editingGroup}
@@ -315,6 +319,7 @@ export default function GroupList({user}: {user: User}) {
}
/>
</Modal>
{renderSearch()}
<table className="bg-mti-purple-ultralight/40 w-full rounded-xl">
<thead>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -109,23 +109,6 @@ export default function UserList({
.finally(reload);
};
const updateAccountType = (user: User, type: Type) => {
if (!confirm(`Are you sure you want to update ${user.name}'s account from ${capitalize(user.type)} to ${capitalize(type)}?`)) return;
axios
.post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {
...user,
type,
})
.then(() => {
toast.success("User type updated successfully!");
reload();
})
.catch(() => {
toast.error("Something went wrong!", {toastId: "update-error"});
});
};
const verifyAccount = (user: User) => {
axios
.post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {