diff --git a/src/components/ExamEditor/index.tsx b/src/components/ExamEditor/index.tsx index a55ff259..599f804b 100644 --- a/src/components/ExamEditor/index.tsx +++ b/src/components/ExamEditor/index.tsx @@ -3,12 +3,12 @@ import SectionRenderer from "./SectionRenderer"; import Checkbox from "../Low/Checkbox"; import Input from "../Low/Input"; import Select from "../Low/Select"; -import { capitalize } from "lodash"; -import { Difficulty } from "@/interfaces/exam"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { toast } from "react-toastify"; -import { ModuleState, SectionState } from "@/stores/examEditor/types"; -import { Module } from "@/interfaces"; +import {capitalize} from "lodash"; +import {Difficulty} from "@/interfaces/exam"; +import {useCallback, useEffect, useMemo, useState} from "react"; +import {toast} from "react-toastify"; +import {ModuleState, SectionState} from "@/stores/examEditor/types"; +import {Module} from "@/interfaces"; import useExamEditorStore from "@/stores/examEditor"; import WritingSettings from "./SettingsEditor/writing"; import ReadingSettings from "./SettingsEditor/reading"; @@ -16,273 +16,243 @@ import LevelSettings from "./SettingsEditor/level"; import ListeningSettings from "./SettingsEditor/listening"; import SpeakingSettings from "./SettingsEditor/speaking"; import ImportOrStartFromScratch from "./ImportExam/ImportOrFromScratch"; -import { defaultSectionSettings } from "@/stores/examEditor/defaults"; +import {defaultSectionSettings} from "@/stores/examEditor/defaults"; import Button from "../Low/Button"; import ResetModule from "./Standalone/ResetModule"; import ListeningInstructions from "./Standalone/ListeningInstructions"; +import {EntityWithRoles} from "@/interfaces/entity"; const DIFFICULTIES: Difficulty[] = ["A1", "A2", "B1", "B2", "C1", "C2"]; -const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => { - const { currentModule, dispatch } = useExamEditorStore(); - const { - sections, - minTimer, - expandedSections, - examLabel, - isPrivate, - difficulty, - sectionLabels, - importModule, - } = useExamEditorStore((state) => state.modules[currentModule]); +const ExamEditor: React.FC<{levelParts?: number; entitiesAllowEditPrivacy: EntityWithRoles[]}> = ({ + levelParts = 0, + entitiesAllowEditPrivacy = [], +}) => { + const {currentModule, dispatch} = useExamEditorStore(); + const {sections, minTimer, expandedSections, examLabel, isPrivate, difficulty, sectionLabels, importModule} = useExamEditorStore( + (state) => state.modules[currentModule], + ); - const [numberOfLevelParts, setNumberOfLevelParts] = useState( - levelParts !== 0 ? levelParts : 1 - ); - const [isResetModuleOpen, setIsResetModuleOpen] = useState(false); + const [numberOfLevelParts, setNumberOfLevelParts] = useState(levelParts !== 0 ? levelParts : 1); + const [isResetModuleOpen, setIsResetModuleOpen] = useState(false); - // For exam edits - useEffect(() => { - if (levelParts !== 0) { - setNumberOfLevelParts(levelParts); - dispatch({ - type: "UPDATE_MODULE", - payload: { - updates: { - sectionLabels: Array.from({ length: levelParts }).map((_, i) => ({ - id: i + 1, - label: `Part ${i + 1}`, - })), - }, - module: "level", - }, - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [levelParts]); + // For exam edits + useEffect(() => { + if (levelParts !== 0) { + setNumberOfLevelParts(levelParts); + dispatch({ + type: "UPDATE_MODULE", + payload: { + updates: { + sectionLabels: Array.from({length: levelParts}).map((_, i) => ({ + id: i + 1, + label: `Part ${i + 1}`, + })), + }, + module: "level", + }, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [levelParts]); - useEffect(() => { - const currentSections = sections; - const currentLabels = sectionLabels; - let updatedSections: SectionState[]; - let updatedLabels: any; - if ( - (currentModule === "level" && - currentSections.length !== currentLabels.length) || - numberOfLevelParts !== currentSections.length - ) { - const newSections = [...currentSections]; - const newLabels = [...currentLabels]; - for (let i = currentLabels.length; i < numberOfLevelParts; i++) { - if (currentSections.length !== numberOfLevelParts) - newSections.push(defaultSectionSettings(currentModule, i + 1)); - newLabels.push({ - id: i + 1, - label: `Part ${i + 1}`, - }); - } - updatedSections = newSections; - updatedLabels = newLabels; - } else if (numberOfLevelParts < currentSections.length) { - updatedSections = currentSections.slice(0, numberOfLevelParts); - updatedLabels = currentLabels.slice(0, numberOfLevelParts); - } else { - return; - } + useEffect(() => { + const currentSections = sections; + const currentLabels = sectionLabels; + let updatedSections: SectionState[]; + let updatedLabels: any; + if ((currentModule === "level" && currentSections.length !== currentLabels.length) || numberOfLevelParts !== currentSections.length) { + const newSections = [...currentSections]; + const newLabels = [...currentLabels]; + for (let i = currentLabels.length; i < numberOfLevelParts; i++) { + if (currentSections.length !== numberOfLevelParts) newSections.push(defaultSectionSettings(currentModule, i + 1)); + newLabels.push({ + id: i + 1, + label: `Part ${i + 1}`, + }); + } + updatedSections = newSections; + updatedLabels = newLabels; + } else if (numberOfLevelParts < currentSections.length) { + updatedSections = currentSections.slice(0, numberOfLevelParts); + updatedLabels = currentLabels.slice(0, numberOfLevelParts); + } else { + return; + } - const updatedExpandedSections = expandedSections.filter((sectionId) => - updatedSections.some((section) => section.sectionId === sectionId) - ); + const updatedExpandedSections = expandedSections.filter((sectionId) => updatedSections.some((section) => section.sectionId === sectionId)); - dispatch({ - type: "UPDATE_MODULE", - payload: { - updates: { - sections: updatedSections, - sectionLabels: updatedLabels, - expandedSections: updatedExpandedSections, - }, - }, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [numberOfLevelParts]); + dispatch({ + type: "UPDATE_MODULE", + payload: { + updates: { + sections: updatedSections, + sectionLabels: updatedLabels, + expandedSections: updatedExpandedSections, + }, + }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [numberOfLevelParts]); - const sectionIds = sections.map((section) => section.sectionId); + const sectionIds = sections.map((section) => section.sectionId); - const updateModule = useCallback( - (updates: Partial) => { - dispatch({ type: "UPDATE_MODULE", payload: { updates } }); - }, - [dispatch] - ); + const updateModule = useCallback( + (updates: Partial) => { + dispatch({type: "UPDATE_MODULE", payload: {updates}}); + }, + [dispatch], + ); - const toggleSection = (sectionId: number) => { - if (expandedSections.length === 1 && sectionIds.includes(sectionId)) { - toast.error("Include at least one section!"); - return; - } - dispatch({ type: "TOGGLE_SECTION", payload: { sectionId } }); - }; + const toggleSection = (sectionId: number) => { + if (expandedSections.length === 1 && sectionIds.includes(sectionId)) { + toast.error("Include at least one section!"); + return; + } + dispatch({type: "TOGGLE_SECTION", payload: {sectionId}}); + }; - const ModuleSettings: Record = { - reading: ReadingSettings, - writing: WritingSettings, - speaking: SpeakingSettings, - listening: ListeningSettings, - level: LevelSettings, - }; + const ModuleSettings: Record = { + reading: ReadingSettings, + writing: WritingSettings, + speaking: SpeakingSettings, + listening: ListeningSettings, + level: LevelSettings, + }; - const Settings = ModuleSettings[currentModule]; - const showImport = - importModule && ["reading", "listening", "level"].includes(currentModule); + const Settings = ModuleSettings[currentModule]; + const showImport = importModule && ["reading", "listening", "level"].includes(currentModule); - const updateLevelParts = (parts: number) => { - setNumberOfLevelParts(parts); - }; + const updateLevelParts = (parts: number) => { + setNumberOfLevelParts(parts); + }; - return ( - <> - {showImport ? ( - - ) : ( - <> - {isResetModuleOpen && ( - - )} -
-
-
- - - updateModule({ - minTimer: parseInt(e) < 15 ? 15 : parseInt(e), - }) - } - value={minTimer} - className="max-w-[300px]" - /> -
-
- - setNumberOfLevelParts(parseInt(v))} - value={numberOfLevelParts} - /> -
- )} -
-
- updateModule({ isPrivate: checked })} - > - Privacy (Only available for Assignments) - -
-
-
-
- - updateModule({ examLabel: text })} - roundness="xl" - value={examLabel} - required - /> -
- {currentModule === "listening" && } - -
-
- -
- -
-
- - )} - - ); + return ( + <> + {showImport ? ( + + ) : ( + <> + {isResetModuleOpen && ( + + )} +
+
+
+ + + updateModule({ + minTimer: parseInt(e) < 15 ? 15 : parseInt(e), + }) + } + value={minTimer} + className="max-w-[300px]" + /> +
+
+ + setNumberOfLevelParts(parseInt(v))} + value={numberOfLevelParts} + /> +
+ )} +
+
+ updateModule({isPrivate: checked})} + disabled={entitiesAllowEditPrivacy.length === 0}> + Privacy (Only available for Assignments) + +
+
+
+
+ + updateModule({examLabel: text})} + roundness="xl" + value={examLabel} + required + /> +
+ {currentModule === "listening" && } + +
+
+ +
+ +
+
+ + )} + + ); }; export default ExamEditor; diff --git a/src/pages/(admin)/Lists/ExamList.tsx b/src/pages/(admin)/Lists/ExamList.tsx index 5b1fb789..5e090ee7 100644 --- a/src/pages/(admin)/Lists/ExamList.tsx +++ b/src/pages/(admin)/Lists/ExamList.tsx @@ -1,33 +1,32 @@ -import { useMemo, useState } from "react"; -import { PERMISSIONS } from "@/constants/userPermissions"; +import {useMemo, useState} 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 {User} from "@/interfaces/user"; import useExamStore from "@/stores/exam"; -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, uniq } from "lodash"; -import { useRouter } from "next/router"; -import { BsBan, BsBanFill, BsCheck, BsCircle, BsPencil, BsStop, BsTrash, BsUpload, BsX } from "react-icons/bs"; -import { toast } from "react-toastify"; -import { useListSearch } from "@/hooks/useListSearch"; +import {capitalize, uniq} from "lodash"; +import {useRouter} from "next/router"; +import {BsBan, BsCheck, BsCircle, BsPencil, BsTrash, BsUpload, BsX} from "react-icons/bs"; +import {toast} from "react-toastify"; +import {useListSearch} from "@/hooks/useListSearch"; import Modal from "@/components/Modal"; -import { checkAccess } from "@/utils/permissions"; +import {checkAccess} from "@/utils/permissions"; import useGroups from "@/hooks/useGroups"; import Button from "@/components/Low/Button"; -import { EntityWithRoles } from "@/interfaces/entity"; -import { FiEdit, FiArrowRight } from 'react-icons/fi'; -import { HiArrowRight } from "react-icons/hi"; -import { BiEdit } from "react-icons/bi"; +import {EntityWithRoles} from "@/interfaces/entity"; +import {BiEdit} from "react-icons/bi"; +import {findBy, mapBy} from "@/utils"; +import {getUserName} from "@/utils/users"; const searchFields = [["module"], ["id"], ["createdBy"]]; -const CLASSES: { [key in Module]: string } = { +const CLASSES: {[key in Module]: string} = { reading: "text-ielts-reading", listening: "text-ielts-listening", speaking: "text-ielts-speaking", @@ -37,45 +36,20 @@ const CLASSES: { [key in Module]: string } = { const columnHelper = createColumnHelper(); -const ExamOwnerSelector = ({ options, exam, onSave }: { options: User[]; exam: Exam; onSave: (owners: string[]) => void }) => { - const [owners, setOwners] = useState(exam.owners || []); - - return ( -
-
- {options.map((c) => ( - - ))} -
- -
- ); -}; - -export default function ExamList({ user, entities }: { user: User; entities: EntityWithRoles[]; }) { +export default function ExamList({user, entities}: {user: User; entities: EntityWithRoles[]}) { const [selectedExam, setSelectedExam] = useState(); - const { exams, reload } = useExams(); - const { users } = useUsers(); - const { groups } = useGroups({ admin: user?.id, userType: user?.type }); + const {exams, reload} = useExams(); + const {users} = useUsers(); - const filteredExams = useMemo(() => exams.filter((e) => { - if (!e.private) return true - return (e.owners || []).includes(user?.id || "") - }), [exams, user?.id]) - - const filteredCorporates = useMemo(() => { - const participantsAndAdmins = uniq(groups.flatMap((x) => [...x.participants, x.admin])).filter((x) => x !== user?.id); - return users.filter((x) => participantsAndAdmins.includes(x.id) && x.type === "corporate"); - }, [users, groups, user]); + const filteredExams = useMemo( + () => + exams.filter((e) => { + if (!e.private) return true; + return (e.entities || []).some((ent) => mapBy(user.entities, "id").includes(ent)); + }), + [exams, user?.entities], + ); const parsedExams = useMemo(() => { return filteredExams.map((exam) => { @@ -93,7 +67,7 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent }); }, [filteredExams, users]); - const { rows: filteredRows, renderSearch } = useListSearch(searchFields, parsedExams); + const {rows: filteredRows, renderSearch} = useListSearch(searchFields, parsedExams); const dispatch = useExamStore((state) => state.dispatch); @@ -108,7 +82,7 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent return; } - dispatch({ type: "INIT_EXAM", payload: { exams: [exam], modules: [module] } }) + dispatch({type: "INIT_EXAM", payload: {exams: [exam], modules: [module]}}); router.push("/exam"); }; @@ -117,7 +91,7 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent if (!confirm(`Are you sure you want to make this ${capitalize(exam.module)} exam ${exam.private ? "public" : "private"}?`)) return; axios - .patch(`/api/exam/${exam.module}/${exam.id}`, { private: !exam.private }) + .patch(`/api/exam/${exam.module}/${exam.id}`, {private: !exam.private}) .then(() => toast.success(`Updated the "${exam.id}" exam`)) .catch((reason) => { if (reason.response.status === 404) { @@ -135,29 +109,6 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent .finally(reload); }; - const updateExam = async (exam: Exam, body: object) => { - if (!confirm(`Are you sure you want to update this ${capitalize(exam.module)} exam?`)) return; - - axios - .patch(`/api/exam/${exam.module}/${exam.id}`, body) - .then(() => toast.success(`Updated the "${exam.id}" exam`)) - .catch((reason) => { - if (reason.response.status === 404) { - toast.error("Exam not found!"); - return; - } - - if (reason.response.status === 403) { - toast.error("You do not have permission to update this exam!"); - return; - } - - toast.error("Something went wrong, please try again later."); - }) - .finally(reload) - .finally(() => setSelectedExam(undefined)); - }; - const deleteExam = async (exam: Exam) => { if (!confirm(`Are you sure you want to delete this ${capitalize(exam.module)} exam?`)) return; @@ -222,12 +173,12 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent }), columnHelper.accessor("createdBy", { header: "Created By", - cell: (info) => info.getValue(), + cell: (info) => (!info.getValue() ? "System" : findBy(users, "id", info.getValue())?.name || "N/A"), }), { header: "", id: "actions", - cell: ({ row }: { row: { original: Exam } }) => { + cell: ({row}: {row: {original: Exam}}) => { return (
{(row.original.owners?.includes(user.id) || checkAccess(user, ["admin", "developer"])) && ( @@ -270,7 +221,7 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent const handleExamEdit = () => { router.push(`/generation?id=${selectedExam!.id}&module=${selectedExam!.module}`); - } + }; return (
@@ -286,30 +237,17 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent
-

- Exam ID: {selectedExam.id} -

+

Exam ID: {selectedExam.id}

-

- Click 'Next' to proceed to the exam editor. -

+

Click 'Next' to proceed to the exam editor.

- -
diff --git a/src/pages/entities/[id]/roles/[role].tsx b/src/pages/entities/[id]/roles/[role].tsx index 907c157b..693b4370 100644 --- a/src/pages/entities/[id]/roles/[role].tsx +++ b/src/pages/entities/[id]/roles/[role].tsx @@ -1,136 +1,132 @@ import Checkbox from "@/components/Low/Checkbox"; import Separator from "@/components/Low/Separator"; -import { useEntityPermission } from "@/hooks/useEntityPermissions"; -import { EntityWithRoles, Role } from "@/interfaces/entity"; -import { User } from "@/interfaces/user"; -import { sessionOptions } from "@/lib/session"; -import { RolePermission } from "@/resources/entityPermissions"; -import { findBy, mapBy, redirect, serialize } from "@/utils"; -import { requestUser } from "@/utils/api"; -import { getEntityWithRoles } from "@/utils/entities.be"; -import { shouldRedirectHome } from "@/utils/navigation.disabled"; -import { doesEntityAllow } from "@/utils/permissions"; -import { isAdmin } from "@/utils/users"; -import { countEntityUsers } from "@/utils/users.be"; +import {useEntityPermission} from "@/hooks/useEntityPermissions"; +import {EntityWithRoles, Role} from "@/interfaces/entity"; +import {User} from "@/interfaces/user"; +import {sessionOptions} from "@/lib/session"; +import {RolePermission} from "@/resources/entityPermissions"; +import {findBy, mapBy, redirect, serialize} from "@/utils"; +import {requestUser} from "@/utils/api"; +import {getEntityWithRoles} from "@/utils/entities.be"; +import {shouldRedirectHome} from "@/utils/navigation.disabled"; +import {doesEntityAllow} from "@/utils/permissions"; +import {isAdmin} from "@/utils/users"; +import {countEntityUsers} from "@/utils/users.be"; import axios from "axios"; -import { withIronSessionSsr } from "iron-session/next"; +import {withIronSessionSsr} from "iron-session/next"; import Head from "next/head"; import Link from "next/link"; -import { useRouter } from "next/router"; -import { Divider } from "primereact/divider"; -import { useState } from "react"; -import { - BsCheck, - BsChevronLeft, - BsTag, - BsTrash, -} from "react-icons/bs"; -import { toast } from "react-toastify"; +import {useRouter} from "next/router"; +import {Divider} from "primereact/divider"; +import {useState} from "react"; +import {BsCheck, BsChevronLeft, BsTag, BsTrash} from "react-icons/bs"; +import {toast} from "react-toastify"; -type PermissionLayout = { label: string, key: RolePermission } +type PermissionLayout = {label: string; key: RolePermission}; const USER_MANAGEMENT: PermissionLayout[] = [ - { label: "View Students", key: "view_students" }, - { label: "View Teachers", key: "view_teachers" }, - { label: "View Corporate Accounts", key: "view_corporates" }, - { label: "View Master Corporate Accounts", key: "view_mastercorporates" }, - { label: "Edit Students", key: "edit_students" }, - { label: "Edit Teachers", key: "edit_teachers" }, - { label: "Edit Corporate Accounts", key: "edit_corporates" }, - { label: "Edit Master Corporate Accounts", key: "edit_mastercorporates" }, - { label: "Delete Students", key: "delete_students" }, - { label: "Delete Teachers", key: "delete_teachers" }, - { label: "Delete Corporate Accounts", key: "delete_corporates" }, - { label: "Delete Master Corporate Accounts", key: "delete_mastercorporates" }, - { label: "Create a Single User", key: "create_user" }, - { label: "Create Users in Batch", key: "create_user_batch" }, - { label: "Create a Single Code", key: "create_code" }, - { label: "Create Codes in Batch", key: "create_code_batch" }, - { label: "Download User List", key: "download_user_list" }, - { label: "View Code List", key: "view_code_list" }, - { label: "Delete Code", key: "delete_code" }, -] + {label: "View Students", key: "view_students"}, + {label: "View Teachers", key: "view_teachers"}, + {label: "View Corporate Accounts", key: "view_corporates"}, + {label: "View Master Corporate Accounts", key: "view_mastercorporates"}, + {label: "Edit Students", key: "edit_students"}, + {label: "Edit Teachers", key: "edit_teachers"}, + {label: "Edit Corporate Accounts", key: "edit_corporates"}, + {label: "Edit Master Corporate Accounts", key: "edit_mastercorporates"}, + {label: "Delete Students", key: "delete_students"}, + {label: "Delete Teachers", key: "delete_teachers"}, + {label: "Delete Corporate Accounts", key: "delete_corporates"}, + {label: "Delete Master Corporate Accounts", key: "delete_mastercorporates"}, + {label: "Create a Single User", key: "create_user"}, + {label: "Create Users in Batch", key: "create_user_batch"}, + {label: "Create a Single Code", key: "create_code"}, + {label: "Create Codes in Batch", key: "create_code_batch"}, + {label: "Download User List", key: "download_user_list"}, + {label: "View Code List", key: "view_code_list"}, + {label: "Delete Code", key: "delete_code"}, +]; const EXAM_MANAGEMENT: PermissionLayout[] = [ - { label: "View Reading", key: "view_reading" }, - { label: "Generate Reading", key: "generate_reading" }, - { label: "Delete Reading", key: "delete_reading" }, - { label: "View Listening", key: "view_listening" }, - { label: "Generate Listening", key: "generate_listening" }, - { label: "Delete Listening", key: "delete_listening" }, - { label: "View Writing", key: "view_writing" }, - { label: "Generate Writing", key: "generate_writing" }, - { label: "Delete Writing", key: "delete_writing" }, - { label: "View Speaking", key: "view_speaking" }, - { label: "Generate Speaking", key: "generate_speaking" }, - { label: "Delete Speaking", key: "delete_speaking" }, - { label: "View Level", key: "view_level" }, - { label: "Generate Level", key: "generate_level" }, - { label: "Delete Level", key: "delete_level" }, - { label: "View Statistics", key: "view_statistics" }, -] + {label: "View Reading", key: "view_reading"}, + {label: "Generate Reading", key: "generate_reading"}, + {label: "Delete Reading", key: "delete_reading"}, + {label: "View Listening", key: "view_listening"}, + {label: "Generate Listening", key: "generate_listening"}, + {label: "Delete Listening", key: "delete_listening"}, + {label: "View Writing", key: "view_writing"}, + {label: "Generate Writing", key: "generate_writing"}, + {label: "Delete Writing", key: "delete_writing"}, + {label: "View Speaking", key: "view_speaking"}, + {label: "Generate Speaking", key: "generate_speaking"}, + {label: "Delete Speaking", key: "delete_speaking"}, + {label: "View Level", key: "view_level"}, + {label: "Generate Level", key: "generate_level"}, + {label: "Delete Level", key: "delete_level"}, + {label: "Set as Private/Public", key: "update_exam_privacy"}, + {label: "View Statistics", key: "view_statistics"}, +]; const CLASSROOM_MANAGEMENT: PermissionLayout[] = [ - { label: "View Classrooms", key: "view_classrooms" }, - { label: "Create Classrooms", key: "create_classroom" }, - { label: "Rename Classrooms", key: "rename_classrooms" }, - { label: "Add to Classroom", key: "add_to_classroom" }, - { label: "Upload to Classroom", key: "upload_classroom" }, - { label: "Remove from Classroom", key: "remove_from_classroom" }, - { label: "Delete Classroom", key: "delete_classroom" }, - { label: "View Student Record", key: "view_student_record" }, - { label: "Download Student Report", key: "download_student_record" }, -] + {label: "View Classrooms", key: "view_classrooms"}, + {label: "Create Classrooms", key: "create_classroom"}, + {label: "Rename Classrooms", key: "rename_classrooms"}, + {label: "Add to Classroom", key: "add_to_classroom"}, + {label: "Upload to Classroom", key: "upload_classroom"}, + {label: "Remove from Classroom", key: "remove_from_classroom"}, + {label: "Delete Classroom", key: "delete_classroom"}, + {label: "View Student Record", key: "view_student_record"}, + {label: "Download Student Report", key: "download_student_record"}, +]; const ENTITY_MANAGEMENT: PermissionLayout[] = [ - { label: "View Entities", key: "view_entities" }, - { label: "View Entity Statistics", key: "view_entity_statistics" }, - { label: "Rename Entity", key: "rename_entity" }, - { label: "Add to Entity", key: "add_to_entity" }, - { label: "Remove from Entity", key: "remove_from_entity" }, - { label: "Delete Entity", key: "delete_entity" }, - { label: "View Entity Roles", key: "view_entity_roles" }, - { label: "Create Entity Role", key: "create_entity_role" }, - { label: "Rename Entity Role", key: "rename_entity_role" }, - { label: "Edit Role Permissions", key: "edit_role_permissions" }, - { label: "Assign Role to User", key: "assign_to_role" }, - { label: "Delete Entity Role", key: "delete_entity_role" }, - { label: "Download Statistics Report", key: "download_statistics_report" }, - { label: "Edit Grading System", key: "edit_grading_system" }, - { label: "View Student Performance", key: "view_student_performance" }, - { label: "Pay for Entity", key: "pay_entity" }, - { label: "View Payment Record", key: "view_payment_record" } -] + {label: "View Entities", key: "view_entities"}, + {label: "View Entity Statistics", key: "view_entity_statistics"}, + {label: "Rename Entity", key: "rename_entity"}, + {label: "Add to Entity", key: "add_to_entity"}, + {label: "Remove from Entity", key: "remove_from_entity"}, + {label: "Delete Entity", key: "delete_entity"}, + {label: "View Entity Roles", key: "view_entity_roles"}, + {label: "Create Entity Role", key: "create_entity_role"}, + {label: "Rename Entity Role", key: "rename_entity_role"}, + {label: "Edit Role Permissions", key: "edit_role_permissions"}, + {label: "Assign Role to User", key: "assign_to_role"}, + {label: "Delete Entity Role", key: "delete_entity_role"}, + {label: "Download Statistics Report", key: "download_statistics_report"}, + {label: "Edit Grading System", key: "edit_grading_system"}, + {label: "View Student Performance", key: "view_student_performance"}, + {label: "Pay for Entity", key: "pay_entity"}, + {label: "View Payment Record", key: "view_payment_record"}, +]; const ASSIGNMENT_MANAGEMENT: PermissionLayout[] = [ - { label: "View Assignments", key: "view_assignments" }, - { label: "Create Assignments", key: "create_assignment" }, - { label: "Start Assignments", key: "start_assignment" }, - { label: "Edit Assignments", key: "edit_assignment" }, - { label: "Delete Assignments", key: "delete_assignment" }, - { label: "Archive Assignments", key: "archive_assignment" }, -] + {label: "View Assignments", key: "view_assignments"}, + {label: "Create Assignments", key: "create_assignment"}, + {label: "Start Assignments", key: "start_assignment"}, + {label: "Edit Assignments", key: "edit_assignment"}, + {label: "Delete Assignments", key: "delete_assignment"}, + {label: "Archive Assignments", key: "archive_assignment"}, +]; -export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { - const user = await requestUser(req, res) - if (!user) return redirect("/login") +export const getServerSideProps = withIronSessionSsr(async ({req, res, params}) => { + const user = await requestUser(req, res); + if (!user) return redirect("/login"); - if (shouldRedirectHome(user)) return redirect("/") + if (shouldRedirectHome(user)) return redirect("/"); - const { id, role } = params as { id: string, role: string }; + const {id, role} = params as {id: string; role: string}; - if (!mapBy(user.entities, 'id').includes(id) && !["admin", "developer"].includes(user.type)) return redirect("/entities") + if (!mapBy(user.entities, "id").includes(id) && !["admin", "developer"].includes(user.type)) return redirect("/entities"); const entity = await getEntityWithRoles(id); - if (!entity) return redirect("/entities") + if (!entity) return redirect("/entities"); - const entityRole = findBy(entity.roles, 'id', role) - if (!entityRole) return redirect(`/entities/${id}/roles`) + const entityRole = findBy(entity.roles, "id", role); + if (!entityRole) return redirect(`/entities/${id}/roles`); - if (!doesEntityAllow(user, entity, "view_entity_roles")) return redirect(`/entities/${id}`) - const disableEdit = !isAdmin(user) && findBy(user.entities, 'id', entity.id)?.role === entityRole.id + if (!doesEntityAllow(user, entity, "view_entity_roles")) return redirect(`/entities/${id}`); + const disableEdit = !isAdmin(user) && findBy(user.entities, "id", entity.id)?.role === entityRole.id; - const userCount = await countEntityUsers(id, { "entities.role": role }); + const userCount = await countEntityUsers(id, {"entities.role": role}); return { props: serialize({ @@ -138,7 +134,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } entity, role: entityRole, userCount, - disableEdit + disableEdit, }), }; }, sessionOptions); @@ -148,19 +144,18 @@ interface Props { entity: EntityWithRoles; role: Role; userCount: number; - disableEdit?: boolean + disableEdit?: boolean; } -export default function EntityRole({ user, entity, role, userCount, disableEdit }: Props) { - const [permissions, setPermissions] = useState(role.permissions) +export default function EntityRole({user, entity, role, userCount, disableEdit}: Props) { + const [permissions, setPermissions] = useState(role.permissions); const [isLoading, setIsLoading] = useState(false); const router = useRouter(); - const canEditPermissions = useEntityPermission(user, entity, "edit_role_permissions") - const canRenameRole = useEntityPermission(user, entity, "rename_entity_role") - const canDeleteRole = useEntityPermission(user, entity, "delete_entity_role") - + const canEditPermissions = useEntityPermission(user, entity, "edit_role_permissions"); + const canRenameRole = useEntityPermission(user, entity, "rename_entity_role"); + const canDeleteRole = useEntityPermission(user, entity, "delete_entity_role"); const renameRole = () => { if (!canRenameRole || disableEdit) return; @@ -170,7 +165,7 @@ export default function EntityRole({ user, entity, role, userCount, disableEdit setIsLoading(true); axios - .patch(`/api/roles/${role.id}`, { label }) + .patch(`/api/roles/${role.id}`, {label}) .then(() => { toast.success("The role has been updated successfully!"); router.replace(router.asPath); @@ -202,12 +197,12 @@ export default function EntityRole({ user, entity, role, userCount, disableEdit }; const editPermissions = () => { - if (!canEditPermissions || disableEdit) return + if (!canEditPermissions || disableEdit) return; setIsLoading(true); axios - .patch(`/api/roles/${role.id}`, { permissions }) + .patch(`/api/roles/${role.id}`, {permissions}) .then(() => { toast.success("This role has been successfully updated!"); router.replace(router.asPath); @@ -217,21 +212,23 @@ export default function EntityRole({ user, entity, role, userCount, disableEdit toast.error("Something went wrong!"); }) .finally(() => setIsLoading(false)); - } + }; const enableCheckbox = (permission: RolePermission) => { - if (!canEditPermissions || disableEdit) return false - return doesEntityAllow(user, entity, permission) - } + if (!canEditPermissions || disableEdit) return false; + return doesEntityAllow(user, entity, permission); + }; - const togglePermissions = (p: RolePermission) => setPermissions(prev => prev.includes(p) ? prev.filter(x => x !== p) : [...prev, p]) + const togglePermissions = (p: RolePermission) => setPermissions((prev) => (prev.includes(p) ? prev.filter((x) => x !== p) : [...prev, p])); const toggleMultiplePermissions = (p: RolePermission[]) => - setPermissions(prev => [...prev.filter(x => !p.includes(x)), ...(p.every(x => prev.includes(x)) ? [] : p)]) + setPermissions((prev) => [...prev.filter((x) => !p.includes(x)), ...(p.every((x) => prev.includes(x)) ? [] : p)]); return ( <> - {role.label} | {entity.label} | EnCoach + + {role.label} | {entity.label} | EnCoach + -

{role.label} Role ({userCount} users)

+

+ {role.label} Role ({userCount} users) +

@@ -286,16 +285,19 @@ export default function EntityRole({ user, entity, role, userCount, disableEdit User Management permissions.includes(k))} - onChange={() => toggleMultiplePermissions(mapBy(USER_MANAGEMENT, 'key').filter(enableCheckbox))} - > + isChecked={mapBy(USER_MANAGEMENT, "key").every((k) => permissions.includes(k))} + onChange={() => toggleMultiplePermissions(mapBy(USER_MANAGEMENT, "key").filter(enableCheckbox))}> Select all
- {USER_MANAGEMENT.map(({ label, key }) => ( - togglePermissions(key)}> + {USER_MANAGEMENT.map(({label, key}) => ( + togglePermissions(key)}> {label} ))} @@ -307,16 +309,19 @@ export default function EntityRole({ user, entity, role, userCount, disableEdit Exam Management permissions.includes(k))} - onChange={() => toggleMultiplePermissions(mapBy(EXAM_MANAGEMENT, 'key').filter(enableCheckbox))} - > + isChecked={mapBy(EXAM_MANAGEMENT, "key").every((k) => permissions.includes(k))} + onChange={() => toggleMultiplePermissions(mapBy(EXAM_MANAGEMENT, "key").filter(enableCheckbox))}> Select all
- {EXAM_MANAGEMENT.map(({ label, key }) => ( - togglePermissions(key)}> + {EXAM_MANAGEMENT.map(({label, key}) => ( + togglePermissions(key)}> {label} ))} @@ -328,16 +333,19 @@ export default function EntityRole({ user, entity, role, userCount, disableEdit Clasroom Management permissions.includes(k))} - onChange={() => toggleMultiplePermissions(mapBy(CLASSROOM_MANAGEMENT, 'key').filter(enableCheckbox))} - > + isChecked={mapBy(CLASSROOM_MANAGEMENT, "key").every((k) => permissions.includes(k))} + onChange={() => toggleMultiplePermissions(mapBy(CLASSROOM_MANAGEMENT, "key").filter(enableCheckbox))}> Select all
- {CLASSROOM_MANAGEMENT.map(({ label, key }) => ( - togglePermissions(key)}> + {CLASSROOM_MANAGEMENT.map(({label, key}) => ( + togglePermissions(key)}> {label} ))} @@ -349,16 +357,19 @@ export default function EntityRole({ user, entity, role, userCount, disableEdit Entity Management permissions.includes(k))} - onChange={() => toggleMultiplePermissions(mapBy(ENTITY_MANAGEMENT, 'key').filter(enableCheckbox))} - > + isChecked={mapBy(ENTITY_MANAGEMENT, "key").every((k) => permissions.includes(k))} + onChange={() => toggleMultiplePermissions(mapBy(ENTITY_MANAGEMENT, "key").filter(enableCheckbox))}> Select all
- {ENTITY_MANAGEMENT.map(({ label, key }) => ( - togglePermissions(key)}> + {ENTITY_MANAGEMENT.map(({label, key}) => ( + togglePermissions(key)}> {label} ))} @@ -370,16 +381,19 @@ export default function EntityRole({ user, entity, role, userCount, disableEdit Assignment Management permissions.includes(k))} - onChange={() => toggleMultiplePermissions(mapBy(ASSIGNMENT_MANAGEMENT, 'key').filter(enableCheckbox))} - > + isChecked={mapBy(ASSIGNMENT_MANAGEMENT, "key").every((k) => permissions.includes(k))} + onChange={() => toggleMultiplePermissions(mapBy(ASSIGNMENT_MANAGEMENT, "key").filter(enableCheckbox))}> Select all
- {ASSIGNMENT_MANAGEMENT.map(({ label, key }) => ( - togglePermissions(key)}> + {ASSIGNMENT_MANAGEMENT.map(({label, key}) => ( + togglePermissions(key)}> {label} ))} diff --git a/src/pages/generation.tsx b/src/pages/generation.tsx index 65903172..17172374 100644 --- a/src/pages/generation.tsx +++ b/src/pages/generation.tsx @@ -1,39 +1,40 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import { withIronSessionSsr } from "iron-session/next"; -import { sessionOptions } from "@/lib/session"; -import { ToastContainer } from "react-toastify"; -import { shouldRedirectHome } from "@/utils/navigation.disabled"; -import { Radio, RadioGroup } from "@headlessui/react"; +import {withIronSessionSsr} from "iron-session/next"; +import {sessionOptions} from "@/lib/session"; +import {ToastContainer} from "react-toastify"; +import {shouldRedirectHome} from "@/utils/navigation.disabled"; +import {Radio, RadioGroup} from "@headlessui/react"; import clsx from "clsx"; -import { MODULE_ARRAY } from "@/utils/moduleUtils"; -import { capitalize } from "lodash"; +import {MODULE_ARRAY} from "@/utils/moduleUtils"; +import {capitalize} from "lodash"; import Input from "@/components/Low/Input"; -import { findAllowedEntities } from "@/utils/permissions"; -import { User } from "@/interfaces/user"; +import {findAllowedEntities} from "@/utils/permissions"; +import {User} from "@/interfaces/user"; import useExamEditorStore from "@/stores/examEditor"; import ExamEditorStore from "@/stores/examEditor/types"; import ExamEditor from "@/components/ExamEditor"; -import { mapBy, redirect, serialize } from "@/utils"; -import { requestUser } from "@/utils/api"; -import { Module } from "@/interfaces"; -import { getExam, } from "@/utils/exams.be"; -import { Exam, Exercise, InteractiveSpeakingExercise, ListeningPart, SpeakingExercise } from "@/interfaces/exam"; -import { useEffect, useState } from "react"; -import { getEntitiesWithRoles } from "@/utils/entities.be"; -import { isAdmin } from "@/utils/users"; +import {mapBy, redirect, serialize} from "@/utils"; +import {requestUser} from "@/utils/api"; +import {Module} from "@/interfaces"; +import {getExam} from "@/utils/exams.be"; +import {Exam, Exercise, InteractiveSpeakingExercise, ListeningPart, SpeakingExercise} from "@/interfaces/exam"; +import {useEffect, useState} from "react"; +import {getEntitiesWithRoles} from "@/utils/entities.be"; +import {isAdmin} from "@/utils/users"; import axios from "axios"; +import {EntityWithRoles} from "@/interfaces/entity"; -type Permission = { [key in Module]: boolean } +type Permission = {[key in Module]: boolean}; -export const getServerSideProps = withIronSessionSsr(async ({ req, res, query }) => { - const user = await requestUser(req, res) - if (!user) return redirect("/login") +export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) => { + const user = await requestUser(req, res); + if (!user) return redirect("/login"); - if (shouldRedirectHome(user)) return redirect("/") + if (shouldRedirectHome(user)) return redirect("/"); - const entityIDs = mapBy(user.entities, 'id') - const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDs) + const entityIDs = mapBy(user.entities, "id"); + const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDs); const permissions: Permission = { reading: findAllowedEntities(user, entities, `generate_reading`).length > 0, @@ -41,29 +42,46 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, query }) writing: findAllowedEntities(user, entities, `generate_writing`).length > 0, speaking: findAllowedEntities(user, entities, `generate_speaking`).length > 0, level: findAllowedEntities(user, entities, `generate_level`).length > 0, - } + }; - if (Object.keys(permissions).every(p => !permissions[p as Module])) return redirect("/") + const entitiesAllowEditPrivacy = findAllowedEntities(user, entities, "update_exam_privacy"); + console.log(entitiesAllowEditPrivacy); - const { id, module: examModule } = query as { id?: string, module?: Module } - if (!id || !examModule) return { props: serialize({ user, permissions }) }; + if (Object.keys(permissions).every((p) => !permissions[p as Module])) return redirect("/"); + + const {id, module: examModule} = query as {id?: string; module?: Module}; + if (!id || !examModule) return {props: serialize({user, permissions})}; //if (!permissions[module]) return redirect("/generation") - const exam = await getExam(examModule, id) - if (!exam) return redirect("/generation") + const exam = await getExam(examModule, id); + if (!exam) return redirect("/generation"); return { - props: serialize({ id, user, exam, examModule, permissions }), + props: serialize({id, user, exam, examModule, permissions, entitiesAllowEditPrivacy}), }; }, sessionOptions); -export default function Generation({ id, user, exam, examModule, permissions }: { id: string, user: User; exam?: Exam, examModule?: Module, permissions: Permission }) { - const { title, currentModule, modules, dispatch } = useExamEditorStore(); +export default function Generation({ + id, + user, + exam, + examModule, + permissions, + entitiesAllowEditPrivacy, +}: { + id: string; + user: User; + exam?: Exam; + examModule?: Module; + permissions: Permission; + entitiesAllowEditPrivacy: EntityWithRoles[]; +}) { + const {title, currentModule, modules, dispatch} = useExamEditorStore(); const [examLevelParts, setExamLevelParts] = useState(undefined); const updateRoot = (updates: Partial) => { - dispatch({ type: 'UPDATE_ROOT', payload: { updates } }); + dispatch({type: "UPDATE_ROOT", payload: {updates}}); }; useEffect(() => { @@ -71,16 +89,16 @@ export default function Generation({ id, user, exam, examModule, permissions }: if (examModule === "level" && exam.module === "level") { setExamLevelParts(exam.parts.length); } - updateRoot({currentModule: examModule}) - dispatch({ type: "INIT_EXAM_EDIT", payload: { exam, id, examModule } }) + updateRoot({currentModule: examModule}); + dispatch({type: "INIT_EXAM_EDIT", payload: {exam, id, examModule}}); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id, exam, module]) + }, [id, exam, module]); useEffect(() => { const fetchAvatars = async () => { const response = await axios.get("/api/exam/avatars"); - updateRoot({ speakingAvatars: response.data }); + updateRoot({speakingAvatars: response.data}); }; fetchAvatars(); @@ -96,48 +114,61 @@ export default function Generation({ id, user, exam, examModule, permissions }: URL.revokeObjectURL(state.writing.academic_url); } - state.listening.sections.forEach(section => { + state.listening.sections.forEach((section) => { const listeningPart = section.state as ListeningPart; if (listeningPart.audio?.source) { URL.revokeObjectURL(listeningPart.audio.source); dispatch({ - type: "UPDATE_SECTION_SINGLE_FIELD", payload: { - sectionId: section.sectionId, module: "listening", field: "state", value: { ...listeningPart, audio: undefined } - } - }) + type: "UPDATE_SECTION_SINGLE_FIELD", + payload: { + sectionId: section.sectionId, + module: "listening", + field: "state", + value: {...listeningPart, audio: undefined}, + }, + }); } }); - if (state.listening.instructionsState.customInstructionsURL.startsWith('blob:')) { + if (state.listening.instructionsState.customInstructionsURL.startsWith("blob:")) { URL.revokeObjectURL(state.listening.instructionsState.customInstructionsURL); } - state.speaking.sections.forEach(section => { + state.speaking.sections.forEach((section) => { const sectionState = section.state as Exercise; - if (sectionState.type === 'speaking') { + if (sectionState.type === "speaking") { const speakingExercise = sectionState as SpeakingExercise; URL.revokeObjectURL(speakingExercise.video_url); dispatch({ - type: "UPDATE_SECTION_SINGLE_FIELD", payload: { - sectionId: section.sectionId, module: "listening", field: "state", value: { ...speakingExercise, video_url: undefined } - } - }) + type: "UPDATE_SECTION_SINGLE_FIELD", + payload: { + sectionId: section.sectionId, + module: "listening", + field: "state", + value: {...speakingExercise, video_url: undefined}, + }, + }); } - if (sectionState.type === 'interactiveSpeaking') { + if (sectionState.type === "interactiveSpeaking") { const interactiveSpeaking = sectionState as InteractiveSpeakingExercise; - interactiveSpeaking.prompts.forEach(prompt => { + interactiveSpeaking.prompts.forEach((prompt) => { URL.revokeObjectURL(prompt.video_url); }); dispatch({ - type: "UPDATE_SECTION_SINGLE_FIELD", payload: { - sectionId: section.sectionId, module: "listening", field: "state", value: { - ...interactiveSpeaking, prompts: interactiveSpeaking.prompts.map((p) => ({ ...p, video_url: undefined })) - } - } - }) + type: "UPDATE_SECTION_SINGLE_FIELD", + payload: { + sectionId: section.sectionId, + module: "listening", + field: "state", + value: { + ...interactiveSpeaking, + prompts: interactiveSpeaking.prompts.map((p) => ({...p, video_url: undefined})), + }, + }, + }); } }); - dispatch({ type: 'FULL_RESET' }); + dispatch({type: "FULL_RESET"}); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -163,7 +194,7 @@ export default function Generation({ id, user, exam, examModule, permissions }: placeholder="Insert a title here" name="title" label="Title" - onChange={(title) => updateRoot({ title })} + onChange={(title) => updateRoot({title})} roundness="xl" value={title} defaultValue={title} @@ -172,44 +203,46 @@ export default function Generation({ id, user, exam, examModule, permissions }: updateRoot({ currentModule })} + onChange={(currentModule) => updateRoot({currentModule})} className="flex flex-row flex-wrap w-full gap-4 -md:justify-center justify-between"> - {[...MODULE_ARRAY].filter(m => permissions[m]).map((x) => ( - - {({ checked }) => ( - - {capitalize(x)} - - )} - - ))} + {[...MODULE_ARRAY] + .filter((m) => permissions[m]) + .map((x) => ( + + {({checked}) => ( + + {capitalize(x)} + + )} + + ))}
- + )} diff --git a/src/resources/entityPermissions.ts b/src/resources/entityPermissions.ts index 76e1b169..cb4212cc 100644 --- a/src/resources/entityPermissions.ts +++ b/src/resources/entityPermissions.ts @@ -1,72 +1,73 @@ export type RolePermission = - "view_students" | - "view_teachers" | - "view_corporates" | - "view_mastercorporates" | - "edit_students" | - "edit_teachers" | - "edit_corporates" | - "edit_mastercorporates" | - "delete_students" | - "delete_teachers" | - "delete_corporates" | - "delete_mastercorporates" | - "generate_reading" | - "view_reading" | - "delete_reading" | - "generate_listening" | - "view_listening" | - "delete_listening" | - "generate_writing" | - "view_writing" | - "delete_writing" | - "generate_speaking" | - "view_speaking" | - "delete_speaking" | - "generate_level" | - "view_level" | - "delete_level" | - "view_classrooms" | - "create_classroom" | - "rename_classrooms" | - "add_to_classroom" | - "remove_from_classroom" | - "delete_classroom" | - "view_entities" | - "rename_entity" | - "add_to_entity" | - "remove_from_entity" | - "delete_entity" | - "view_entity_roles" | - "create_entity_role" | - "rename_entity_role" | - "edit_role_permissions" | - "assign_to_role" | - "delete_entity_role" | - "view_assignments" | - "create_assignment" | - "edit_assignment" | - "delete_assignment" | - "start_assignment" | - "archive_assignment" | - "view_entity_statistics" | - "create_user" | - "create_user_batch" | - "create_code" | - "create_code_batch" | - "view_code_list" | - "delete_code" | - "view_statistics" | - "download_statistics_report" | - "edit_grading_system" | - "view_student_performance" | - "upload_classroom" | - "download_user_list" | - "view_student_record" | - "download_student_record" | - "pay_entity" | - "view_payment_record" | - "view_approval_workflows" + | "view_students" + | "view_teachers" + | "view_corporates" + | "view_mastercorporates" + | "edit_students" + | "edit_teachers" + | "edit_corporates" + | "edit_mastercorporates" + | "delete_students" + | "delete_teachers" + | "delete_corporates" + | "delete_mastercorporates" + | "generate_reading" + | "view_reading" + | "delete_reading" + | "generate_listening" + | "view_listening" + | "delete_listening" + | "generate_writing" + | "view_writing" + | "delete_writing" + | "generate_speaking" + | "view_speaking" + | "delete_speaking" + | "generate_level" + | "view_level" + | "delete_level" + | "view_classrooms" + | "create_classroom" + | "rename_classrooms" + | "add_to_classroom" + | "remove_from_classroom" + | "delete_classroom" + | "view_entities" + | "rename_entity" + | "add_to_entity" + | "remove_from_entity" + | "delete_entity" + | "view_entity_roles" + | "create_entity_role" + | "rename_entity_role" + | "edit_role_permissions" + | "assign_to_role" + | "delete_entity_role" + | "view_assignments" + | "create_assignment" + | "edit_assignment" + | "delete_assignment" + | "start_assignment" + | "archive_assignment" + | "view_entity_statistics" + | "create_user" + | "create_user_batch" + | "create_code" + | "create_code_batch" + | "view_code_list" + | "delete_code" + | "view_statistics" + | "download_statistics_report" + | "edit_grading_system" + | "view_student_performance" + | "upload_classroom" + | "download_user_list" + | "view_student_record" + | "download_student_record" + | "pay_entity" + | "view_payment_record" + | "view_approval_workflows" + | "update_exam_privacy"; export const DEFAULT_PERMISSIONS: RolePermission[] = [ "view_students", @@ -76,8 +77,8 @@ export const DEFAULT_PERMISSIONS: RolePermission[] = [ "view_entity_roles", "view_statistics", "download_statistics_report", - "view_approval_workflows" -] + "view_approval_workflows", +]; export const ADMIN_PERMISSIONS: RolePermission[] = [ "view_students", @@ -146,5 +147,6 @@ export const ADMIN_PERMISSIONS: RolePermission[] = [ "view_student_record", "download_student_record", "pay_entity", - "view_payment_record" -] + "view_payment_record", + "update_exam_privacy", +]; diff --git a/src/stores/examEditor/index.ts b/src/stores/examEditor/index.ts index 7db76092..ceb957ed 100644 --- a/src/stores/examEditor/index.ts +++ b/src/stores/examEditor/index.ts @@ -1,25 +1,25 @@ import defaultModuleSettings from "./defaults"; -import { Action, rootReducer } from "./reducers"; +import {Action, rootReducer} from "./reducers"; import ExamEditorStore from "./types"; -import { create } from "zustand"; - +import {create} from "zustand"; const useExamEditorStore = create< - ExamEditorStore & { - dispatch: (action: Action) => void; - }>((set) => ({ - title: "", - globalEdit: [], - currentModule: "reading", - speakingAvatars: [], - modules: { - reading: defaultModuleSettings("reading", 60), - writing: defaultModuleSettings("writing", 60), - speaking: defaultModuleSettings("speaking", 14), - listening: defaultModuleSettings("listening", 30), - level: defaultModuleSettings("level", 60) - }, - dispatch: (action) => set((state) => rootReducer(state, action)) - })); + ExamEditorStore & { + dispatch: (action: Action) => void; + } +>((set) => ({ + title: "", + globalEdit: [], + currentModule: "reading", + speakingAvatars: [], + modules: { + reading: defaultModuleSettings("reading", 60), + writing: defaultModuleSettings("writing", 60), + speaking: defaultModuleSettings("speaking", 14), + listening: defaultModuleSettings("listening", 30), + level: defaultModuleSettings("level", 60), + }, + dispatch: (action) => set((state) => rootReducer(state, action)), +})); export default useExamEditorStore; diff --git a/src/utils/users.ts b/src/utils/users.ts index 319be4f1..c30ed239 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -1,7 +1,7 @@ -import { WithLabeledEntities } from "@/interfaces/entity"; -import { User } from "@/interfaces/user"; -import { USER_TYPE_LABELS } from "@/resources/user"; -import { capitalize } from "lodash"; +import {WithLabeledEntities} from "@/interfaces/entity"; +import {User} from "@/interfaces/user"; +import {USER_TYPE_LABELS} from "@/resources/user"; +import {capitalize} from "lodash"; import moment from "moment"; export interface UserListRow { @@ -22,7 +22,7 @@ export const exportListToExcel = (rowUsers: WithLabeledEntities[]) => { name: user.name, email: user.email, type: USER_TYPE_LABELS[user.type], - entities: user.entities.map((e) => e.label).join(', '), + entities: user.entities.map((e) => e.label).join(", "), expiryDate: user.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).format("DD/MM/YYYY") : "Unlimited", country: user.demographicInformation?.country || "N/A", phone: user.demographicInformation?.phone || "N/A", @@ -41,8 +41,7 @@ export const exportListToExcel = (rowUsers: WithLabeledEntities[]) => { export const getUserName = (user?: User) => { if (!user) return "N/A"; - if (user.type === "corporate" || user.type === "mastercorporate") return user.name; return user.name; }; -export const isAdmin = (user: User) => ["admin", "developer"].includes(user?.type) +export const isAdmin = (user: User) => ["admin", "developer"].includes(user?.type);