diff --git a/src/dashboards/Teacher.tsx b/src/dashboards/Teacher.tsx index 855dfc69..41548af7 100644 --- a/src/dashboards/Teacher.tsx +++ b/src/dashboards/Teacher.tsx @@ -46,6 +46,7 @@ import ProgressBar from "@/components/Low/ProgressBar"; import AssignmentCreator from "./AssignmentCreator"; import AssignmentView from "./AssignmentView"; import { getUserCorporate } from "@/utils/groups"; +import { checkAccess } from "@/utils/permissions"; interface Props { user: User; @@ -373,13 +374,15 @@ export default function TeacherDashboard({ user }: Props) { ).toFixed(1)} color="purple" /> - setPage("groups")} - /> + {checkAccess(user, ["teacher", "developer"], "viewGroup") && ( + setPage("groups")} + /> + )}
setPage("assignments")} className="bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300" diff --git a/src/interfaces/exam.ts b/src/interfaces/exam.ts index 1918882d..18c222b2 100644 --- a/src/interfaces/exam.ts +++ b/src/interfaces/exam.ts @@ -5,15 +5,20 @@ export type Variant = "full" | "partial"; export type InstructorGender = "male" | "female" | "varied"; export type Difficulty = "easy" | "medium" | "hard"; -export interface ReadingExam { - parts: ReadingPart[]; +interface ExamBase { id: string; - module: "reading"; + module: Module; minTimer: number; - type: "academic" | "general"; isDiagnostic: boolean; variant?: Variant; difficulty?: Difficulty; + createdBy?: string; // option as it has been added later + createdAt?: string; // option as it has been added later +} +export interface ReadingExam extends ExamBase { + module: "reading"; + parts: ReadingPart[]; + type: "academic" | "general"; } export interface ReadingPart { @@ -24,14 +29,9 @@ export interface ReadingPart { exercises: Exercise[]; } -export interface LevelExam { +export interface LevelExam extends ExamBase { module: "level"; - id: string; parts: LevelPart[]; - minTimer: number; - isDiagnostic: boolean; - variant?: Variant; - difficulty?: Difficulty; } export interface LevelPart { @@ -39,14 +39,9 @@ export interface LevelPart { exercises: Exercise[]; } -export interface ListeningExam { +export interface ListeningExam extends ExamBase { parts: ListeningPart[]; - id: string; module: "listening"; - minTimer: number; - isDiagnostic: boolean; - variant?: Variant; - difficulty?: Difficulty; } export interface ListeningPart { @@ -72,14 +67,9 @@ export interface UserSolution { isDisabled?: boolean; } -export interface WritingExam { +export interface WritingExam extends ExamBase { module: "writing"; - id: string; exercises: WritingExercise[]; - minTimer: number; - isDiagnostic: boolean; - variant?: Variant; - difficulty?: Difficulty; } interface WordCounter { @@ -87,15 +77,10 @@ interface WordCounter { limit: number; } -export interface SpeakingExam { - id: string; +export interface SpeakingExam extends ExamBase { module: "speaking"; exercises: (SpeakingExercise | InteractiveSpeakingExercise)[]; - minTimer: number; - isDiagnostic: boolean; - variant?: Variant; instructorGender: InstructorGender; - difficulty?: Difficulty; } export type Exercise = diff --git a/src/interfaces/permissions.ts b/src/interfaces/permissions.ts index 2e448af5..808564b6 100644 --- a/src/interfaces/permissions.ts +++ b/src/interfaces/permissions.ts @@ -26,18 +26,26 @@ export const permissions = [ "viewCorporate", "viewCountryManager", "viewAdmin", + "viewGroup", + "viewCodes", // edit data "editStudent", "editTeacher", "editCorporate", "editCountryManager", "editAdmin", + "editGroup", // delete data "deleteStudent", "deleteTeacher", "deleteCorporate", "deleteCountryManager", "deleteAdmin", + "deleteGroup", + "deleteCodes", + // create options + "createGroup", + "createCodes" ] as const; export type PermissionType = (typeof permissions)[keyof typeof permissions]; diff --git a/src/pages/(admin)/BatchCodeGenerator.tsx b/src/pages/(admin)/BatchCodeGenerator.tsx index 7a0d5f52..3e55e6da 100644 --- a/src/pages/(admin)/BatchCodeGenerator.tsx +++ b/src/pages/(admin)/BatchCodeGenerator.tsx @@ -16,7 +16,7 @@ import { useFilePicker } from "use-file-picker"; import readXlsxFile from "read-excel-file"; import Modal from "@/components/Modal"; import { BsFileEarmarkEaselFill, BsQuestionCircleFill } from "react-icons/bs"; -import { checkAccess } from "@/utils/permissions"; +import { checkAccess, getTypesOfUser } from "@/utils/permissions"; import { PermissionType } from "@/interfaces/permissions"; const EMAIL_REGEX = new RegExp( /^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/ @@ -297,7 +297,12 @@ export default function BatchCodeGenerator({ user }: { user: User }) { {filesContent.length > 0 ? filesContent[0].name : "Choose a file"} {user && - checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && ( + checkAccess(user, [ + "developer", + "admin", + "corporate", + "mastercorporate", + ]) && ( <>
); diff --git a/src/pages/(admin)/BatchCreateUser.tsx b/src/pages/(admin)/BatchCreateUser.tsx new file mode 100644 index 00000000..b484fd12 --- /dev/null +++ b/src/pages/(admin)/BatchCreateUser.tsx @@ -0,0 +1,243 @@ +import Button from "@/components/Low/Button"; +import useUsers from "@/hooks/useUsers"; +import { Type as UserType, User } from "@/interfaces/user"; +import axios from "axios"; +import { uniqBy } from "lodash"; +import { useEffect, useState } from "react"; +import { toast } from "react-toastify"; +import { useFilePicker } from "use-file-picker"; +import readXlsxFile from "read-excel-file"; +import Modal from "@/components/Modal"; +import { BsQuestionCircleFill } from "react-icons/bs"; +import { PermissionType } from "@/interfaces/permissions"; +const EMAIL_REGEX = new RegExp( + /^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/ +); + +type Type = Exclude + +const USER_TYPE_LABELS: {[key in Type]: string} = { + student: "Student", + teacher: "Teacher", + corporate: "Corporate", +}; + +const USER_TYPE_PERMISSIONS: { + [key in Type]: { perm: PermissionType | undefined; list: Type[] }; +} = { + student: { + perm: "createCodeStudent", + list: [], + }, + teacher: { + perm: "createCodeTeacher", + list: [], + }, + corporate: { + perm: "createCodeCorporate", + list: ["student", "teacher"], + }, +}; + +export default function BatchCreateUser({ user }: { user: User }) { + const [infos, setInfos] = useState< + { email: string; name: string; passport_id:string, type: Type, demographicInformation: { + country: string, + passport_id:string, + phone: string + } }[] + >([]); + const [isLoading, setIsLoading] = useState(false); + const [type, setType] = useState("student"); + const [showHelp, setShowHelp] = useState(false); + + const { users } = useUsers(); + + const { openFilePicker, filesContent, clear } = useFilePicker({ + accept: ".xlsx", + multiple: false, + readAs: "ArrayBuffer", + }); + + + useEffect(() => { + if (filesContent.length > 0) { + const file = filesContent[0]; + readXlsxFile(file.content).then((rows) => { + try { + const information = uniqBy( + rows + .map((row) => { + const [ + firstName, + lastName, + country, + passport_id, + email, + phone, + group + ] = row as string[]; + return EMAIL_REGEX.test(email.toString().trim()) + ? { + email: email.toString().trim().toLowerCase(), + name: `${firstName ?? ""} ${lastName ?? ""}`.trim().toLowerCase(), + type: type, + passport_id: passport_id?.toString().trim() || undefined, + groupName: group, + demographicInformation: { + country: country, + passport_id: passport_id?.toString().trim() || undefined, + phone, + } + } + : undefined; + }) + .filter((x) => !!x) as typeof infos, + (x) => x.email + ); + + if (information.length === 0) { + toast.error( + "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!" + ); + return clear(); + } + + setInfos(information); + } catch { + toast.error( + "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!" + ); + return clear(); + } + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filesContent]); + + const makeUsers = async () => { + const newUsers = infos.filter( + (x) => !users.map((u) => u.email).includes(x.email) + ); + const confirmed = confirm( + `You are about to add ${newUsers.length}, are you sure you want to continue?` + ) + if (!confirmed) + return; + if (newUsers.length > 0) + { + setIsLoading(true); + Promise.all(newUsers.map(async (user) => { + await axios.post("/api/make_user", user) + })).then((res) =>{ + toast.success( + `Successfully added ${newUsers.length} user(s)!` + )}).finally(() => { + return clear(); + }) + } + setIsLoading(false); + setInfos([]); + }; + + + return ( + <> + setShowHelp(false)} + title="Excel File Format" + > +
+ Please upload an Excel file with the following format: + + + + + + + + + + + + +
+ First Name + + Last Name + Country + Passport/National ID + E-mail + Phone Number + + Group Name +
+ + Notes: +
    +
  • - All incorrect e-mails will be ignored;
  • +
  • - All already registered e-mails will be ignored;
  • +
  • + - You may have a header row with the format above, however, it + is not necessary; +
  • +
  • + - All of the e-mails in the file will receive an e-mail to join + EnCoach with the role selected below. +
  • +
+
+
+
+
+
+ +
setShowHelp(true)} + > + +
+
+ + + {user && ( + + )} + +
+ + ); +} diff --git a/src/pages/(admin)/CodeGenerator.tsx b/src/pages/(admin)/CodeGenerator.tsx index 30c5007a..c34c78fa 100644 --- a/src/pages/(admin)/CodeGenerator.tsx +++ b/src/pages/(admin)/CodeGenerator.tsx @@ -130,47 +130,48 @@ export default function CodeGenerator({ user }: { user: User }) { ))} )} - {user && - checkAccess(user, ["developer", "admin", "corporate"]) && ( - <> -
- - - Enabled - -
- {isExpiryDateEnabled && ( - - moment(date).isAfter(new Date()) && - (user.subscriptionExpirationDate - ? moment(date).isBefore(user.subscriptionExpirationDate) - : true) - } - dateFormat="dd/MM/yyyy" - selected={expiryDate} - onChange={(date) => setExpiryDate(date)} - /> - )} - - )} - + {user && checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && ( + <> +
+ + + Enabled + +
+ {isExpiryDateEnabled && ( + + moment(date).isAfter(new Date()) && + (user.subscriptionExpirationDate + ? moment(date).isBefore(user.subscriptionExpirationDate) + : true) + } + dateFormat="dd/MM/yyyy" + selected={expiryDate} + onChange={(date) => setExpiryDate(date)} + /> + )} + + )} + {checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"], 'createCodes') && ( + + )} diff --git a/src/pages/(admin)/Lists/CodeList.tsx b/src/pages/(admin)/Lists/CodeList.tsx index 7ac20b3a..cc8d0f79 100644 --- a/src/pages/(admin)/Lists/CodeList.tsx +++ b/src/pages/(admin)/Lists/CodeList.tsx @@ -19,6 +19,7 @@ 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"; const columnHelper = createColumnHelper(); @@ -147,6 +148,12 @@ export default function CodeList({ user }: { user: User }) { .finally(reload); }; + const allowedToDelete = checkAccess( + user, + ["developer", "admin", "corporate", "mastercorporate"], + "deleteCodes" + ); + const defaultColumns = [ columnHelper.accessor("code", { id: "codeCheckbox", @@ -209,7 +216,7 @@ export default function CodeList({ user }: { user: User }) { cell: ({ row }: { row: { original: Code } }) => { return (
- {!row.original.userId && ( + {allowedToDelete && !row.original.userId && (
+ {allowedToDelete && (
{selectedCodes.length} code(s) selected
+ )}
diff --git a/src/pages/(admin)/Lists/ExamList.tsx b/src/pages/(admin)/Lists/ExamList.tsx index efd212ca..71f685c1 100644 --- a/src/pages/(admin)/Lists/ExamList.tsx +++ b/src/pages/(admin)/Lists/ExamList.tsx @@ -1,154 +1,223 @@ -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"; -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 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(); +export default function ExamList({ user }: { user: User }) { + const { exams, reload } = useExams(); + const { users } = useUsers(); - const setExams = useExamStore((state) => state.setExams); - const setSelectedModules = useExamStore((state) => state.setSelectedModules); + const parsedExams = useMemo(() => { + return exams.map((exam) => { + if (exam.createdBy) { + const user = users.find((u) => u.id === exam.createdBy); + if (!user) return exam; - const router = useRouter(); + return { + ...exam, + createdBy: user.type === "developer" ? "system" : user.name, + }; + } - 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", - }); + return exam; + }); + }, [exams, users]); - return; - } + const setExams = useExamStore((state) => state.setExams); + const setSelectedModules = useExamStore((state) => state.setSelectedModules); - setExams([exam]); - setSelectedModules([module]); + const router = useRouter(); - router.push("/exercises"); - }; + 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 deleteExam = async (exam: Exam) => { - if (!confirm(`Are you sure you want to delete this ${capitalize(exam.module)} exam?`)) return; + 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; - } + setExams([exam]); + setSelectedModules([module]); - if (reason.response.status === 403) { - toast.error("You do not have permission to delete this exam!"); - return; - } + router.push("/exercises"); + }; - toast.error("Something went wrong, please try again later."); - }) - .finally(reload); - }; + const deleteExam = async (exam: Exam) => { + if ( + !confirm( + `Are you sure you want to delete this ${capitalize(exam.module)} exam?` + ) + ) + return; - const getTotalExercises = (exam: Exam) => { - if (exam.module === "reading" || exam.module === "listening" || exam.module === "level") { - return countExercises(exam.parts.flatMap((x) => x.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; + } - return countExercises(exam.exercises); - }; + if (reason.response.status === 403) { + toast.error("You do not have permission to delete this exam!"); + return; + } - 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), - }), - { - 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)}> - -
- )} -
- ); - }, - }, - ]; + toast.error("Something went wrong, please try again later."); + }) + .finally(reload); + }; - const table = useReactTable({ - data: exams, - columns: defaultColumns, - getCoreRowModel: getCoreRowModel(), - }); + const getTotalExercises = (exam: Exam) => { + if ( + exam.module === "reading" || + exam.module === "listening" || + exam.module === "level" + ) { + return countExercises(exam.parts.flatMap((x) => x.exercises)); + } - 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())} -
- ); + return countExercises(exam.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 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 table = useReactTable({ + data: parsedExams, + columns: defaultColumns, + getCoreRowModel: getCoreRowModel(), + }); + + 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())} +
+ ); } diff --git a/src/pages/(admin)/Lists/GroupList.tsx b/src/pages/(admin)/Lists/GroupList.tsx index ae36a5cd..2d65ed8a 100644 --- a/src/pages/(admin)/Lists/GroupList.tsx +++ b/src/pages/(admin)/Lists/GroupList.tsx @@ -3,335 +3,474 @@ import Input from "@/components/Low/Input"; import Modal from "@/components/Modal"; import useGroups from "@/hooks/useGroups"; import useUsers from "@/hooks/useUsers"; -import {CorporateUser, Group, User} from "@/interfaces/user"; -import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; +import { CorporateUser, Group, User } from "@/interfaces/user"; +import { + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; import axios from "axios"; -import {capitalize, uniq} from "lodash"; -import {useEffect, useState} from "react"; -import {BsPencil, BsQuestionCircleFill, BsTrash} from "react-icons/bs"; +import { capitalize, uniq } from "lodash"; +import { useEffect, useState } from "react"; +import { BsPencil, BsQuestionCircleFill, BsTrash } from "react-icons/bs"; import Select from "react-select"; -import {toast} from "react-toastify"; +import { toast } from "react-toastify"; import readXlsxFile from "read-excel-file"; -import {useFilePicker} from "use-file-picker"; -import {getUserCorporate} from "@/utils/groups"; +import { useFilePicker } from "use-file-picker"; +import { getUserCorporate } from "@/utils/groups"; import { isAgentUser, isCorporateUser } from "@/resources/user"; +import { checkAccess } from "@/utils/permissions"; const columnHelper = createColumnHelper(); -const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/); +const EMAIL_REGEX = new RegExp( + /^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/ +); -const LinkedCorporate = ({userId, users, groups}: {userId: string, users: User[], groups: Group[]}) => { - const [companyName, setCompanyName] = useState(""); - const [isLoading, setIsLoading] = useState(false); +const LinkedCorporate = ({ + userId, + users, + groups, +}: { + userId: string; + users: User[]; + groups: Group[]; +}) => { + const [companyName, setCompanyName] = useState(""); + const [isLoading, setIsLoading] = useState(false); - useEffect(() => { - const user = users.find((u) => u.id === userId) - if (!user) return setCompanyName("") - - if (isCorporateUser(user)) return setCompanyName(user.corporateInformation?.companyInformation?.name || user.name) - if (isAgentUser(user)) return setCompanyName(user.agentInformation?.companyName || user.name) - - const belongingGroups = groups.filter((x) => x.participants.includes(userId)) - const belongingGroupsAdmins = belongingGroups.map((x) => users.find((u) => u.id === x.admin)).filter((x) => !!x && isCorporateUser(x)) - - if (belongingGroupsAdmins.length === 0) return setCompanyName("") - - const admin = (belongingGroupsAdmins[0] as CorporateUser) - setCompanyName(admin.corporateInformation?.companyInformation.name || admin.name) - }, [userId, users, groups]); + useEffect(() => { + const user = users.find((u) => u.id === userId); + if (!user) return setCompanyName(""); - return isLoading ? Loading... : <>{companyName}; + if (isCorporateUser(user)) + return setCompanyName( + user.corporateInformation?.companyInformation?.name || user.name + ); + if (isAgentUser(user)) + return setCompanyName(user.agentInformation?.companyName || user.name); + + const belongingGroups = groups.filter((x) => + x.participants.includes(userId) + ); + const belongingGroupsAdmins = belongingGroups + .map((x) => users.find((u) => u.id === x.admin)) + .filter((x) => !!x && isCorporateUser(x)); + + if (belongingGroupsAdmins.length === 0) return setCompanyName(""); + + const admin = belongingGroupsAdmins[0] as CorporateUser; + setCompanyName( + admin.corporateInformation?.companyInformation.name || admin.name + ); + }, [userId, users, groups]); + + return isLoading ? ( + Loading... + ) : ( + <>{companyName} + ); }; interface CreateDialogProps { - user: User; - users: User[]; - group?: Group; - onClose: () => void; + user: User; + users: User[]; + group?: Group; + onClose: () => void; } -const CreatePanel = ({user, users, group, onClose}: CreateDialogProps) => { - const [name, setName] = useState(group?.name || undefined); - const [admin, setAdmin] = useState(group?.admin || user.id); - const [participants, setParticipants] = useState(group?.participants || []); - const [isLoading, setIsLoading] = useState(false); +const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => { + const [name, setName] = useState( + group?.name || undefined + ); + const [admin, setAdmin] = useState(group?.admin || user.id); + const [participants, setParticipants] = useState( + group?.participants || [] + ); + const [isLoading, setIsLoading] = useState(false); - const {openFilePicker, filesContent, clear} = useFilePicker({ - accept: ".xlsx", - multiple: false, - readAs: "ArrayBuffer", - }); + const { openFilePicker, filesContent, clear } = useFilePicker({ + accept: ".xlsx", + multiple: false, + readAs: "ArrayBuffer", + }); - useEffect(() => { - if (filesContent.length > 0) { - setIsLoading(true); + useEffect(() => { + if (filesContent.length > 0) { + setIsLoading(true); - const file = filesContent[0]; - readXlsxFile(file.content).then((rows) => { - const emails = uniq( - rows - .map((row) => { - const [email] = row as string[]; - return EMAIL_REGEX.test(email) && !users.map((u) => u.email).includes(email) ? email.toString().trim() : undefined; - }) - .filter((x) => !!x), - ); + const file = filesContent[0]; + readXlsxFile(file.content).then((rows) => { + const emails = uniq( + rows + .map((row) => { + const [email] = row as string[]; + return EMAIL_REGEX.test(email) && + !users.map((u) => u.email).includes(email) + ? email.toString().trim() + : undefined; + }) + .filter((x) => !!x) + ); - if (emails.length === 0) { - toast.error("Please upload an Excel file containing e-mails!"); - clear(); - setIsLoading(false); - return; - } + if (emails.length === 0) { + toast.error("Please upload an Excel file containing e-mails!"); + clear(); + setIsLoading(false); + return; + } - const emailUsers = [...new Set(emails)].map((x) => users.find((y) => y.email.toLowerCase() === x)).filter((x) => x !== undefined); - const filteredUsers = emailUsers.filter( - (x) => - ((user.type === "developer" || user.type === "admin" || user.type === "corporate" || user.type === "mastercorporate") && - (x?.type === "student" || x?.type === "teacher")) || - (user.type === "teacher" && x?.type === "student"), - ); + const emailUsers = [...new Set(emails)] + .map((x) => users.find((y) => y.email.toLowerCase() === x)) + .filter((x) => x !== undefined); + const filteredUsers = emailUsers.filter( + (x) => + ((user.type === "developer" || + user.type === "admin" || + user.type === "corporate" || + user.type === "mastercorporate") && + (x?.type === "student" || x?.type === "teacher")) || + (user.type === "teacher" && x?.type === "student") + ); - setParticipants(filteredUsers.filter((x) => !!x).map((x) => x!.id)); - toast.success( - user.type !== "teacher" - ? "Added all teachers and students found in the file you've provided!" - : "Added all students found in the file you've provided!", - {toastId: "upload-success"}, - ); - setIsLoading(false); - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filesContent, user.type, users]); + setParticipants(filteredUsers.filter((x) => !!x).map((x) => x!.id)); + toast.success( + user.type !== "teacher" + ? "Added all teachers and students found in the file you've provided!" + : "Added all students found in the file you've provided!", + { toastId: "upload-success" } + ); + setIsLoading(false); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filesContent, user.type, users]); - const submit = () => { - setIsLoading(true); + const submit = () => { + setIsLoading(true); - if (name !== group?.name && (name === "Students" || name === "Teachers")) { - toast.error("That group name is reserved and cannot be used, please enter another one."); - setIsLoading(false); - return; - } + if (name !== group?.name && (name === "Students" || name === "Teachers")) { + toast.error( + "That group name is reserved and cannot be used, please enter another one." + ); + setIsLoading(false); + return; + } - (group ? axios.patch : axios.post)(group ? `/api/groups/${group.id}` : "/api/groups", {name, admin, participants}) - .then(() => { - toast.success(`Group "${name}" ${group ? "edited" : "created"} successfully`); - return true; - }) - .catch(() => { - toast.error("Something went wrong, please try again later!"); - return false; - }) - .finally(() => { - setIsLoading(false); - onClose(); - }); - }; + (group ? axios.patch : axios.post)( + group ? `/api/groups/${group.id}` : "/api/groups", + { name, admin, participants } + ) + .then(() => { + toast.success( + `Group "${name}" ${group ? "edited" : "created"} successfully` + ); + return true; + }) + .catch(() => { + toast.error("Something went wrong, please try again later!"); + return false; + }) + .finally(() => { + setIsLoading(false); + onClose(); + }); + }; - return ( -
-
- -
-
- -
- -
-
-
- +
+
+ +
+ +
+
+
+