Merge, do not push to develop yet, Listening.tsx is not updated

This commit is contained in:
Carlos-Mesquita
2024-11-26 10:33:02 +00:00
44 changed files with 1989 additions and 1452 deletions

View File

@@ -1,12 +1,16 @@
import Button from "@/components/Low/Button";
import Input from "@/components/Low/Input";
import {Grading, Step} from "@/interfaces";
import {User} from "@/interfaces/user";
import {CEFR_STEPS, GENERAL_STEPS, IELTS_STEPS, TOFEL_STEPS} from "@/resources/grading";
import Select from "@/components/Low/Select";
import { Grading, Step } from "@/interfaces";
import { Entity } from "@/interfaces/entity";
import { User } from "@/interfaces/user";
import { CEFR_STEPS, GENERAL_STEPS, IELTS_STEPS, TOFEL_STEPS } from "@/resources/grading";
import { checkAccess } from "@/utils/permissions";
import axios from "axios";
import {useEffect, useState} from "react";
import {BsPlusCircle, BsTrash} from "react-icons/bs";
import {toast} from "react-toastify";
import clsx from "clsx";
import { useEffect, useState } from "react";
import { BsPlusCircle, BsTrash } from "react-icons/bs";
import { toast } from "react-toastify";
const areStepsOverlapped = (steps: Step[]) => {
for (let i = 0; i < steps.length; i++) {
@@ -21,9 +25,24 @@ const areStepsOverlapped = (steps: Step[]) => {
return false;
};
export default function CorporateGradingSystem({user, defaultSteps, mutate}: {user: User; defaultSteps: Step[]; mutate: (steps: Step[]) => void}) {
interface Props {
user: User;
entitiesGrading: Grading[];
entities: Entity[]
mutate: () => void
}
export default function CorporateGradingSystem({ user, entitiesGrading = [], entities = [], mutate }: Props) {
const [entity, setEntity] = useState(entitiesGrading[0]?.entity || undefined)
const [isLoading, setIsLoading] = useState(false);
const [steps, setSteps] = useState<Step[]>(defaultSteps || []);
const [steps, setSteps] = useState<Step[]>([]);
useEffect(() => {
if (entity) {
const entitySteps = entitiesGrading.find(e => e.entity === entity)!.steps
setSteps(entitySteps || [])
}
}, [entitiesGrading, entity])
const saveGradingSystem = () => {
if (!steps.every((x) => x.min < x.max)) return toast.error("One of your steps has a minimum threshold inferior to its superior threshold.");
@@ -37,9 +56,9 @@ export default function CorporateGradingSystem({user, defaultSteps, mutate}: {us
setIsLoading(true);
axios
.post("/api/grading", {user: user.id, steps})
.post("/api/grading", { user: user.id, entity, steps })
.then(() => toast.success("Your grading system has been saved!"))
.then(() => mutate(steps))
.then(mutate)
.catch(() => toast.error("Something went wrong, please try again later"))
.finally(() => setIsLoading(false));
};
@@ -47,6 +66,15 @@ export default function CorporateGradingSystem({user, defaultSteps, mutate}: {us
return (
<div className="flex flex-col gap-4 border p-4 border-mti-gray-platinum rounded-xl">
<label className="font-normal text-base text-mti-gray-dim">Grading System</label>
<div className={clsx("flex flex-col gap-4")}>
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
<Select
defaultValue={{ value: (entities || [])[0]?.id, label: (entities || [])[0]?.label }}
options={entities.map((e) => ({ value: e.id, label: e.label }))}
onChange={(e) => setEntity(e?.value || undefined)}
isClearable={checkAccess(user, ["admin", "developer"])}
/>
</div>
<label className="font-normal text-base text-mti-gray-dim">Preset Systems</label>
<div className="grid grid-cols-4 gap-4">
@@ -73,7 +101,7 @@ export default function CorporateGradingSystem({user, defaultSteps, mutate}: {us
value={step.min}
type="number"
disabled={index === 0 || isLoading}
onChange={(e) => setSteps((prev) => prev.map((x, i) => (i === index ? {...x, min: parseInt(e)} : x)))}
onChange={(e) => setSteps((prev) => prev.map((x, i) => (i === index ? { ...x, min: parseInt(e) } : x)))}
name="min"
/>
<Input
@@ -81,7 +109,7 @@ export default function CorporateGradingSystem({user, defaultSteps, mutate}: {us
value={step.label}
type="text"
disabled={isLoading}
onChange={(e) => setSteps((prev) => prev.map((x, i) => (i === index ? {...x, label: e} : x)))}
onChange={(e) => setSteps((prev) => prev.map((x, i) => (i === index ? { ...x, label: e } : x)))}
name="min"
/>
<Input
@@ -89,7 +117,7 @@ export default function CorporateGradingSystem({user, defaultSteps, mutate}: {us
value={step.max}
type="number"
disabled={index === steps.length - 1 || isLoading}
onChange={(e) => setSteps((prev) => prev.map((x, i) => (i === index ? {...x, max: parseInt(e)} : x)))}
onChange={(e) => setSteps((prev) => prev.map((x, i) => (i === index ? { ...x, max: parseInt(e) } : x)))}
name="max"
/>
</div>
@@ -110,7 +138,7 @@ export default function CorporateGradingSystem({user, defaultSteps, mutate}: {us
className="w-full flex items-center justify-center"
disabled={isLoading}
onClick={() => {
const item = {min: steps[index === 0 ? 0 : index - 1].max + 1, max: steps[index + 1].min - 1, label: ""};
const item = { min: steps[index === 0 ? 0 : index - 1].max + 1, max: steps[index + 1].min - 1, label: "" };
setSteps((prev) => [...prev.slice(0, index + 1), item, ...prev.slice(index + 1, steps.length)]);
}}>
<BsPlusCircle />

View File

@@ -1,30 +1,30 @@
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 { Type, 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, BsBanFill, BsCheck, BsCircle, BsPencil, BsStop, 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";
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",
@@ -34,7 +34,7 @@ const CLASSES: {[key in Module]: string} = {
const columnHelper = createColumnHelper<Exam>();
const ExamOwnerSelector = ({options, exam, onSave}: {options: User[]; exam: Exam; onSave: (owners: string[]) => void}) => {
const ExamOwnerSelector = ({ options, exam, onSave }: { options: User[]; exam: Exam; onSave: (owners: string[]) => void }) => {
const [owners, setOwners] = useState(exam.owners || []);
return (
@@ -57,12 +57,12 @@ const ExamOwnerSelector = ({options, exam, onSave}: {options: User[]; exam: Exam
);
};
export default function ExamList({user, entities}: {user: User; entities: EntityWithRoles[];}) {
export default function ExamList({ user, entities }: { user: User; entities: EntityWithRoles[]; }) {
const [selectedExam, setSelectedExam] = useState<Exam>();
const {exams, reload} = useExams();
const {users} = useUsers();
const {groups} = useGroups({admin: user?.id, userType: user?.type});
const { exams, reload } = useExams();
const { users } = useUsers();
const { groups } = useGroups({ admin: user?.id, userType: user?.type });
const filteredExams = useMemo(() => exams.filter((e) => {
if (!e.private) return true
@@ -90,7 +90,7 @@ export default function ExamList({user, entities}: {user: User; entities: Entity
});
}, [filteredExams, users]);
const {rows: filteredRows, renderSearch} = useListSearch<Exam>(searchFields, parsedExams);
const { rows: filteredRows, renderSearch } = useListSearch<Exam>(searchFields, parsedExams);
const dispatch = useExamStore((state) => state.dispatch);
@@ -107,14 +107,14 @@ export default function ExamList({user, entities}: {user: User; entities: Entity
}
dispatch({type: "INIT_EXAM", payload: {exams: [exam], modules: [module]}})
router.push("/exercises");
router.push("/exam");
};
const privatizeExam = async (exam: Exam) => {
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) {
@@ -224,7 +224,7 @@ export default function ExamList({user, entities}: {user: User; entities: Entity
{
header: "",
id: "actions",
cell: ({row}: {row: {original: Exam}}) => {
cell: ({ row }: { row: { original: Exam } }) => {
return (
<div className="flex gap-4">
{(row.original.owners?.includes(user.id) || checkAccess(user, ["admin", "developer"])) && (
@@ -270,7 +270,7 @@ export default function ExamList({user, entities}: {user: User; entities: Entity
{renderSearch()}
<Modal isOpen={!!selectedExam} title={`Edit Exam Owners - ${selectedExam?.id}`} onClose={() => setSelectedExam(undefined)}>
{!!selectedExam ? (
<ExamOwnerSelector options={filteredCorporates} exam={selectedExam} onSave={(owners) => updateExam(selectedExam, {owners})} />
<ExamOwnerSelector options={filteredCorporates} exam={selectedExam} onSave={(owners) => updateExam(selectedExam, { owners })} />
) : (
<div />
)}
@@ -301,4 +301,4 @@ export default function ExamList({user, entities}: {user: User; entities: Entity
</table>
</div>
);
}
}