diff --git a/src/pages/(admin)/Lists/ExamList.tsx b/src/pages/(admin)/Lists/ExamList.tsx index 71f685c1..93ffb05c 100644 --- a/src/pages/(admin)/Lists/ExamList.tsx +++ b/src/pages/(admin)/Lists/ExamList.tsx @@ -1,223 +1,195 @@ -import { useMemo } from "react"; -import { PERMISSIONS } from "@/constants/userPermissions"; +import {useMemo} from "react"; +import {PERMISSIONS} from "@/constants/userPermissions"; import useExams from "@/hooks/useExams"; import useUsers from "@/hooks/useUsers"; -import { Module } from "@/interfaces"; -import { Exam } from "@/interfaces/exam"; -import { Type, User } from "@/interfaces/user"; +import {Module} from "@/interfaces"; +import {Exam} from "@/interfaces/exam"; +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 {getExamById} from "@/utils/exams"; +import {countExercises} from "@/utils/moduleUtils"; +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 {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 CLASSES: { [key in Module]: string } = { - reading: "text-ielts-reading", - listening: "text-ielts-listening", - speaking: "text-ielts-speaking", - writing: "text-ielts-writing", - level: "text-ielts-level", +const searchFields = [["module"], ["id"], ["createdBy"]]; + +const CLASSES: {[key in Module]: string} = { + reading: "text-ielts-reading", + listening: "text-ielts-listening", + speaking: "text-ielts-speaking", + writing: "text-ielts-writing", + level: "text-ielts-level", }; const columnHelper = createColumnHelper(); -export default function ExamList({ user }: { user: User }) { - const { exams, reload } = useExams(); - const { users } = useUsers(); +export default function ExamList({user}: {user: User}) { + const {exams, reload} = useExams(); + const {users} = useUsers(); - const parsedExams = useMemo(() => { - return exams.map((exam) => { - if (exam.createdBy) { - const user = users.find((u) => u.id === exam.createdBy); - if (!user) return exam; + const parsedExams = useMemo(() => { + return exams.map((exam) => { + if (exam.createdBy) { + const user = users.find((u) => u.id === exam.createdBy); + if (!user) return exam; - return { - ...exam, - createdBy: user.type === "developer" ? "system" : user.name, - }; - } + return { + ...exam, + createdBy: user.type === "developer" ? "system" : user.name, + }; + } - return exam; - }); - }, [exams, users]); + return exam; + }); + }, [exams, users]); - const setExams = useExamStore((state) => state.setExams); - const setSelectedModules = useExamStore((state) => state.setSelectedModules); + const {rows: filteredRows, renderSearch} = useListSearch(searchFields, parsedExams); - const router = useRouter(); + const setExams = useExamStore((state) => state.setExams); + const setSelectedModules = useExamStore((state) => state.setSelectedModules); - 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", - { - toastId: "invalid-exam-id", - } - ); + const router = useRouter(); - return; - } + 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", { + toastId: "invalid-exam-id", + }); - setExams([exam]); - setSelectedModules([module]); + return; + } - router.push("/exercises"); - }; + setExams([exam]); + setSelectedModules([module]); - const deleteExam = async (exam: Exam) => { - if ( - !confirm( - `Are you sure you want to delete this ${capitalize(exam.module)} exam?` - ) - ) - return; + router.push("/exercises"); + }; - axios - .delete(`/api/exam/${exam.module}/${exam.id}`) - .then(() => toast.success(`Deleted the "${exam.id}" exam`)) - .catch((reason) => { - if (reason.response.status === 404) { - toast.error("Exam not found!"); - return; - } + const deleteExam = async (exam: Exam) => { + if (!confirm(`Are you sure you want to delete this ${capitalize(exam.module)} exam?`)) return; - if (reason.response.status === 403) { - toast.error("You do not have permission to delete this exam!"); - return; - } + axios + .delete(`/api/exam/${exam.module}/${exam.id}`) + .then(() => toast.success(`Deleted the "${exam.id}" exam`)) + .catch((reason) => { + if (reason.response.status === 404) { + toast.error("Exam not found!"); + return; + } - toast.error("Something went wrong, please try again later."); - }) - .finally(reload); - }; + if (reason.response.status === 403) { + toast.error("You do not have permission to delete this exam!"); + return; + } - const getTotalExercises = (exam: Exam) => { - if ( - exam.module === "reading" || - exam.module === "listening" || - exam.module === "level" - ) { - return countExercises(exam.parts.flatMap((x) => x.exercises)); - } + toast.error("Something went wrong, please try again later."); + }) + .finally(reload); + }; - return countExercises(exam.exercises); - }; + const getTotalExercises = (exam: Exam) => { + if (exam.module === "reading" || exam.module === "listening" || exam.module === "level") { + return countExercises(exam.parts.flatMap((x) => x.exercises)); + } - const defaultColumns = [ - columnHelper.accessor("id", { - header: "ID", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("module", { - header: "Module", - cell: (info) => ( - - {capitalize(info.getValue())} - - ), - }), - columnHelper.accessor((x) => getTotalExercises(x), { - header: "Exercises", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("minTimer", { - header: "Timer", - cell: (info) => <>{info.getValue()} minute(s), - }), - columnHelper.accessor("createdAt", { - header: "Created At", - cell: (info) => { - const value = info.getValue(); - if (value) { - return new Date(value).toLocaleDateString(); - } + return countExercises(exam.exercises); + }; - return null; - }, - }), - columnHelper.accessor("createdBy", { - header: "Created By", - cell: (info) => info.getValue(), - }), - { - header: "", - id: "actions", - cell: ({ row }: { row: { original: Exam } }) => { - return ( -
-
- await loadExam(row.original.module, row.original.id) - } - > - -
- {PERMISSIONS.examManagement.delete.includes(user.type) && ( -
deleteExam(row.original)} - > - -
- )} -
- ); - }, - }, - ]; + const defaultColumns = [ + columnHelper.accessor("id", { + header: "ID", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("module", { + header: "Module", + cell: (info) => {capitalize(info.getValue())}, + }), + columnHelper.accessor((x) => getTotalExercises(x), { + header: "Exercises", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("minTimer", { + header: "Timer", + cell: (info) => <>{info.getValue()} minute(s), + }), + columnHelper.accessor("createdAt", { + header: "Created At", + cell: (info) => { + const value = info.getValue(); + if (value) { + return new Date(value).toLocaleDateString(); + } - const table = useReactTable({ - data: parsedExams, - columns: defaultColumns, - getCoreRowModel: getCoreRowModel(), - }); + return null; + }, + }), + columnHelper.accessor("createdBy", { + header: "Created By", + cell: (info) => info.getValue(), + }), + { + header: "", + id: "actions", + cell: ({row}: {row: {original: Exam}}) => { + return ( +
+
await loadExam(row.original.module, row.original.id)}> + +
+ {PERMISSIONS.examManagement.delete.includes(user.type) && ( +
deleteExam(row.original)}> + +
+ )} +
+ ); + }, + }, + ]; - 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())} -
- ); + const table = useReactTable({ + data: filteredRows, + columns: defaultColumns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ {renderSearch()} + + + {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/(admin)/Lists/GroupList.tsx b/src/pages/(admin)/Lists/GroupList.tsx index 04dc72e7..54948f7a 100644 --- a/src/pages/(admin)/Lists/GroupList.tsx +++ b/src/pages/(admin)/Lists/GroupList.tsx @@ -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(); 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(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 ( -
+
+ {renderSearch()} {table.getHeaderGroups().map((headerGroup) => ( diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index 5aa8526e..e3379c7e 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -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}`, {