Merge branch 'feature/level-file-upload' of https://bitbucket.org/ecropdev/ielts-ui into feature/level-file-upload
This commit is contained in:
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import {BsClock, BsXCircle} from "react-icons/bs";
|
||||
import clsx from "clsx";
|
||||
import {Stat, User} from "@/interfaces/user";
|
||||
import {Module} from "@/interfaces";
|
||||
import {Module, Step} from "@/interfaces";
|
||||
import ai_usage from "@/utils/ai.detection";
|
||||
import {calculateBandScore} from "@/utils/score";
|
||||
import moment from "moment";
|
||||
@@ -77,6 +77,7 @@ interface StatsGridItemProps {
|
||||
assignments: Assignment[];
|
||||
users: User[];
|
||||
training?: boolean;
|
||||
gradingSystem?: Step[];
|
||||
selectedTrainingExams?: string[];
|
||||
maxTrainingExams?: number;
|
||||
setSelectedTrainingExams?: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
@@ -97,6 +98,7 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
||||
users,
|
||||
training,
|
||||
selectedTrainingExams,
|
||||
gradingSystem,
|
||||
setSelectedTrainingExams,
|
||||
setExams,
|
||||
setShowSolutions,
|
||||
@@ -214,10 +216,14 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-row gap-2">
|
||||
<span className={textColor}>
|
||||
Level{" "}
|
||||
{(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)}
|
||||
</span>
|
||||
{!!assignment && (assignment.released || assignment.released === undefined) && (
|
||||
<span className={textColor}>
|
||||
Level{" "}
|
||||
{(
|
||||
aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length
|
||||
).toFixed(1)}
|
||||
</span>
|
||||
)}
|
||||
{shouldRenderPDFIcon() && renderPdfIcon(session, textColor, textColor)}
|
||||
</div>
|
||||
{examNumber === undefined ? (
|
||||
@@ -242,9 +248,9 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
||||
|
||||
<div className="w-full flex flex-col gap-1">
|
||||
<div className={clsx("grid grid-cols-4 gap-2 place-items-start w-full -md:mt-2", examNumber !== undefined && "pr-10")}>
|
||||
{aggregatedLevels.map(({module, level}) => (
|
||||
<ModuleBadge key={module} module={module} level={level} />
|
||||
))}
|
||||
{!!assignment &&
|
||||
(assignment.released || assignment.released === undefined) &&
|
||||
aggregatedLevels.map(({module, level}) => <ModuleBadge key={module} module={module} level={level} />)}
|
||||
</div>
|
||||
|
||||
{assignment && (
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import {Step} from "@/interfaces";
|
||||
import {getGradingLabel, getLevelLabel} from "@/utils/score";
|
||||
import clsx from "clsx";
|
||||
import { BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen } from "react-icons/bs";
|
||||
import {BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
|
||||
|
||||
const ModuleBadge: React.FC<{ module: string; level?: number }> = ({ module, level }) => (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex gap-2 items-center w-fit text-white -md:px-4 xl:px-4 md:px-2 py-2 rounded-xl",
|
||||
module === "reading" && "bg-ielts-reading",
|
||||
module === "listening" && "bg-ielts-listening",
|
||||
module === "writing" && "bg-ielts-writing",
|
||||
module === "speaking" && "bg-ielts-speaking",
|
||||
module === "level" && "bg-ielts-level",
|
||||
)}>
|
||||
{module === "reading" && <BsBook className="w-4 h-4" />}
|
||||
{module === "listening" && <BsHeadphones className="w-4 h-4" />}
|
||||
{module === "writing" && <BsPen className="w-4 h-4" />}
|
||||
{module === "speaking" && <BsMegaphone className="w-4 h-4" />}
|
||||
{module === "level" && <BsClipboard className="w-4 h-4" />}
|
||||
{/* do not switch to level && it will convert the 0.0 to 0*/}
|
||||
{level !== undefined && (<span className="text-sm">{level.toFixed(1)}</span>)}
|
||||
</div>
|
||||
const ModuleBadge: React.FC<{module: string; level?: number; gradingSystem?: Step[]}> = ({module, level, gradingSystem}) => (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex gap-2 items-center w-fit text-white -md:px-4 xl:px-4 md:px-2 py-2 rounded-xl",
|
||||
module === "reading" && "bg-ielts-reading",
|
||||
module === "listening" && "bg-ielts-listening",
|
||||
module === "writing" && "bg-ielts-writing",
|
||||
module === "speaking" && "bg-ielts-speaking",
|
||||
module === "level" && "bg-ielts-level",
|
||||
)}>
|
||||
{module === "reading" && <BsBook className="w-4 h-4" />}
|
||||
{module === "listening" && <BsHeadphones className="w-4 h-4" />}
|
||||
{module === "writing" && <BsPen className="w-4 h-4" />}
|
||||
{module === "speaking" && <BsMegaphone className="w-4 h-4" />}
|
||||
{module === "level" && <BsClipboard className="w-4 h-4" />}
|
||||
{/* do not switch to level && it will convert the 0.0 to 0*/}
|
||||
{level !== undefined && (
|
||||
<span className="text-sm">{module === "level" && gradingSystem ? getGradingLabel(level, gradingSystem) : level.toFixed(1)}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ModuleBadge;
|
||||
export default ModuleBadge;
|
||||
|
||||
@@ -25,14 +25,16 @@ import useExams from "@/hooks/useExams";
|
||||
interface Props {
|
||||
isCreating: boolean;
|
||||
users: User[];
|
||||
user: User;
|
||||
groups: Group[];
|
||||
assignment?: Assignment;
|
||||
cancelCreation: () => void;
|
||||
}
|
||||
|
||||
export default function AssignmentCreator({isCreating, assignment, groups, users, cancelCreation}: Props) {
|
||||
export default function AssignmentCreator({isCreating, assignment, user, groups, users, cancelCreation}: Props) {
|
||||
const [selectedModules, setSelectedModules] = useState<Module[]>(assignment?.exams.map((e) => e.module) || []);
|
||||
const [assignees, setAssignees] = useState<string[]>(assignment?.assignees || []);
|
||||
const [teachers, setTeachers] = useState<string[]>(!!assignment ? assignment.teachers || [] : [...(user.type === "teacher" ? [user.id] : [])]);
|
||||
const [name, setName] = useState(
|
||||
assignment?.name ||
|
||||
generate({
|
||||
@@ -53,6 +55,8 @@ export default function AssignmentCreator({isCreating, assignment, groups, users
|
||||
const [instructorGender, setInstructorGender] = useState<InstructorGender>(assignment?.instructorGender || "varied");
|
||||
// creates a new exam for each assignee or just one exam for all assignees
|
||||
const [generateMultiple, setGenerateMultiple] = useState<boolean>(false);
|
||||
const [released, setReleased] = useState<boolean>(false);
|
||||
|
||||
const [useRandomExams, setUseRandomExams] = useState(true);
|
||||
const [examIDs, setExamIDs] = useState<{id: string; module: Module}[]>([]);
|
||||
|
||||
@@ -82,8 +86,10 @@ export default function AssignmentCreator({isCreating, assignment, groups, users
|
||||
endDate,
|
||||
selectedModules,
|
||||
generateMultiple,
|
||||
teachers,
|
||||
variant,
|
||||
instructorGender,
|
||||
released,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(`The assignment "${name}" has been ${assignment ? "updated" : "created"} successfully!`);
|
||||
@@ -373,6 +379,9 @@ export default function AssignmentCreator({isCreating, assignment, groups, users
|
||||
<Checkbox isChecked={generateMultiple} onChange={() => setGenerateMultiple((d) => !d)}>
|
||||
Generate different exams
|
||||
</Checkbox>
|
||||
<Checkbox isChecked={released} onChange={() => setReleased((d) => !d)}>
|
||||
Release automatically
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="flex gap-4 w-full justify-end">
|
||||
<Button className="w-full max-w-[200px]" variant="outline" onClick={cancelCreation} disabled={isLoading} isLoading={isLoading}>
|
||||
|
||||
@@ -524,6 +524,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
{router.asPath === "/#assignments" && (
|
||||
<AssignmentsPage
|
||||
assignments={assignments}
|
||||
user={user}
|
||||
groups={assignmentsGroups}
|
||||
users={assignmentsUsers}
|
||||
reloadAssignments={reloadAssignments}
|
||||
|
||||
@@ -713,6 +713,7 @@ export default function MasterCorporateDashboard({user}: Props) {
|
||||
assignments={assignments}
|
||||
corporateAssignments={corporateAssignments}
|
||||
groups={assignmentsGroups}
|
||||
user={user}
|
||||
users={assignmentsUsers}
|
||||
reloadAssignments={reloadAssignments}
|
||||
isLoading={isAssignmentsLoading}
|
||||
|
||||
@@ -301,6 +301,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
assignments={assignments}
|
||||
groups={assignmentsGroups}
|
||||
users={assignmentsUsers}
|
||||
user={user}
|
||||
reloadAssignments={reloadAssignments}
|
||||
isLoading={isAssignmentsLoading}
|
||||
onBack={() => router.push("/")}
|
||||
|
||||
@@ -16,11 +16,12 @@ interface Props {
|
||||
groups: Group[];
|
||||
users: User[];
|
||||
isLoading: boolean;
|
||||
user: User;
|
||||
onBack: () => void;
|
||||
reloadAssignments: () => void;
|
||||
}
|
||||
|
||||
export default function AssignmentsPage({assignments, corporateAssignments, groups, users, isLoading, onBack, reloadAssignments}: Props) {
|
||||
export default function AssignmentsPage({assignments, corporateAssignments, user, groups, users, isLoading, onBack, reloadAssignments}: Props) {
|
||||
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
|
||||
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
|
||||
|
||||
@@ -39,6 +40,7 @@ export default function AssignmentsPage({assignments, corporateAssignments, grou
|
||||
assignment={selectedAssignment}
|
||||
groups={groups}
|
||||
users={users}
|
||||
user={user}
|
||||
isCreating={isCreatingAssignment}
|
||||
cancelCreation={() => {
|
||||
setIsCreatingAssignment(false);
|
||||
|
||||
@@ -223,7 +223,7 @@ export default function Finish({user, scores, modules, information, solutions, i
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && false && (
|
||||
{!isLoading && (
|
||||
<div className="mb-20 mt-32 flex w-full items-center justify-between gap-9">
|
||||
<span className="max-w-3xl">{moduleResultText(selectedModule, bandScore)}</span>
|
||||
<div className="flex gap-9 px-16">
|
||||
|
||||
@@ -12,6 +12,7 @@ interface ExamBase {
|
||||
isDiagnostic: boolean;
|
||||
variant?: Variant;
|
||||
difficulty?: Difficulty;
|
||||
owners?: string[];
|
||||
shuffle?: boolean;
|
||||
createdBy?: string; // option as it has been added later
|
||||
createdAt?: string; // option as it has been added later
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface Assignment {
|
||||
instructorGender?: InstructorGender;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
teachers?: string[];
|
||||
archived?: boolean;
|
||||
released?: boolean;
|
||||
// unless start is active, the assignment is not visible to the assignees
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useMemo} from "react";
|
||||
import {useMemo, useState} from "react";
|
||||
import {PERMISSIONS} from "@/constants/userPermissions";
|
||||
import useExams from "@/hooks/useExams";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
@@ -11,11 +11,15 @@ 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 {capitalize, uniq} from "lodash";
|
||||
import {useRouter} from "next/router";
|
||||
import {BsBan, BsBanFill, BsCheck, BsCircle, BsStop, BsTrash, BsUpload, BsX} from "react-icons/bs";
|
||||
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 useGroups from "@/hooks/useGroups";
|
||||
import Button from "@/components/Low/Button";
|
||||
|
||||
const searchFields = [["module"], ["id"], ["createdBy"]];
|
||||
|
||||
@@ -29,9 +33,40 @@ const CLASSES: {[key in Module]: string} = {
|
||||
|
||||
const columnHelper = createColumnHelper<Exam>();
|
||||
|
||||
const ExamOwnerSelector = ({options, exam, onSave}: {options: User[]; exam: Exam; onSave: (owners: string[]) => void}) => {
|
||||
const [owners, setOwners] = useState(exam.owners || []);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
<div className="grid grid-cols-4 mt-4">
|
||||
{options.map((c) => (
|
||||
<Button
|
||||
variant={owners.includes(c.id) ? "solid" : "outline"}
|
||||
onClick={() => setOwners((prev) => (prev.includes(c.id) ? prev.filter((x) => x !== c.id) : [...prev, c.id]))}
|
||||
className="max-w-[200px] w-full"
|
||||
key={c.id}>
|
||||
{c.name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<Button onClick={() => onSave(owners)} className="w-full max-w-[200px] self-end">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function ExamList({user}: {user: User}) {
|
||||
const [selectedExam, setSelectedExam] = useState<Exam>();
|
||||
|
||||
const {exams, reload} = useExams();
|
||||
const {users} = useUsers();
|
||||
const {groups} = useGroups({admin: user?.id, userType: user?.type});
|
||||
|
||||
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 parsedExams = useMemo(() => {
|
||||
return exams.map((exam) => {
|
||||
@@ -94,6 +129,29 @@ export default function ExamList({user}: {user: User}) {
|
||||
.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;
|
||||
|
||||
@@ -166,12 +224,21 @@ export default function ExamList({user}: {user: User}) {
|
||||
cell: ({row}: {row: {original: Exam}}) => {
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
data-tip={row.original.private ? "Set as public" : "Set as private"}
|
||||
onClick={async () => await privatizeExam(row.original)}
|
||||
className="cursor-pointer tooltip">
|
||||
{row.original.private ? <BsCircle /> : <BsBan />}
|
||||
</button>
|
||||
{(row.original.owners?.includes(user.id) || checkAccess(user, ["admin", "developer"])) && (
|
||||
<>
|
||||
<button
|
||||
data-tip={row.original.private ? "Set as public" : "Set as private"}
|
||||
onClick={async () => await privatizeExam(row.original)}
|
||||
className="cursor-pointer tooltip">
|
||||
{row.original.private ? <BsCircle /> : <BsBan />}
|
||||
</button>
|
||||
{checkAccess(user, ["admin", "developer", "mastercorporate"]) && (
|
||||
<button data-tip="Edit owners" onClick={() => setSelectedExam(row.original)} className="cursor-pointer tooltip">
|
||||
<BsPencil />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
data-tip="Load exam"
|
||||
className="cursor-pointer tooltip"
|
||||
@@ -198,6 +265,13 @@ export default function ExamList({user}: {user: User}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full h-full">
|
||||
{renderSearch()}
|
||||
<Modal isOpen={!!selectedExam} title={`Edit Exam Owners - ${selectedExam?.id}`} onClose={() => setSelectedExam(undefined)}>
|
||||
{!!selectedExam ? (
|
||||
<ExamOwnerSelector options={filteredCorporates} exam={selectedExam} onSave={(owners) => updateExam(selectedExam, {owners})} />
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</Modal>
|
||||
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
||||
@@ -214,7 +214,7 @@ export default function ExamPage({page, user}: Props) {
|
||||
if (selectedModules.length > 0 && exams.length > 0 && moduleIndex < selectedModules.length) {
|
||||
const nextExam = exams[moduleIndex];
|
||||
|
||||
if (partIndex === -1 && nextExam.module !== "listening") setPartIndex(0);
|
||||
if (partIndex === -1 && nextExam?.module !== "listening") setPartIndex(0);
|
||||
if (exerciseIndex === -1 && !["reading", "listening"].includes(nextExam?.module)) setExerciseIndex(0);
|
||||
setExam(nextExam ? updateExamWithUserSolutions(nextExam) : undefined);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {capitalize, flatten, uniqBy} from "lodash";
|
||||
import {User} from "@/interfaces/user";
|
||||
import moment from "moment";
|
||||
import {sendEmail} from "@/email";
|
||||
import { release } from "os";
|
||||
import {release} from "os";
|
||||
|
||||
const db = getFirestore(app);
|
||||
|
||||
@@ -57,6 +57,7 @@ const generateExams = async (
|
||||
generateMultiple: Boolean,
|
||||
selectedModules: Module[],
|
||||
assignees: string[],
|
||||
userId: string,
|
||||
variant?: Variant,
|
||||
instructorGender?: InstructorGender,
|
||||
): Promise<ExamWithUser[]> => {
|
||||
@@ -87,7 +88,7 @@ const generateExams = async (
|
||||
}
|
||||
|
||||
const selectedModulePromises = selectedModules.map(async (module: Module) => {
|
||||
const exams: Exam[] = await getExams(db, module, "false", undefined, variant, instructorGender);
|
||||
const exams: Exam[] = await getExams(db, module, "false", userId, variant, instructorGender);
|
||||
const exam = exams[getRandomIndex(exams)];
|
||||
|
||||
if (exam) {
|
||||
@@ -122,11 +123,12 @@ async function POST(req: NextApiRequest, res: NextApiResponse) {
|
||||
endDate: string;
|
||||
variant?: Variant;
|
||||
instructorGender?: InstructorGender;
|
||||
released: boolean;
|
||||
};
|
||||
|
||||
const exams: ExamWithUser[] = !!examIDs
|
||||
? examIDs.flatMap((e) => assignees.map((a) => ({...e, assignee: a})))
|
||||
: await generateExams(generateMultiple, selectedModules, assignees, variant, instructorGender);
|
||||
: await generateExams(generateMultiple, selectedModules, assignees, req.session.user!.id, variant, instructorGender);
|
||||
|
||||
if (exams.length === 0) {
|
||||
res.status(400).json({ok: false, error: "No exams found for the selected modules"});
|
||||
@@ -139,7 +141,6 @@ async function POST(req: NextApiRequest, res: NextApiResponse) {
|
||||
results: [],
|
||||
exams,
|
||||
instructorGender,
|
||||
released: false,
|
||||
...body,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {sessionOptions} from "@/lib/session";
|
||||
import {Exam, InstructorGender, Variant} from "@/interfaces/exam";
|
||||
import {getExams} from "@/utils/exams.be";
|
||||
import {Module} from "@/interfaces";
|
||||
import {getUserCorporate} from "@/utils/groups.be";
|
||||
const db = getFirestore(app);
|
||||
|
||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||
@@ -42,11 +43,16 @@ async function POST(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
const {module} = req.query as {module: string};
|
||||
const corporate = await getUserCorporate(req.session.user.id);
|
||||
|
||||
try {
|
||||
const exam = {
|
||||
...req.body,
|
||||
module: module,
|
||||
owners: [
|
||||
...(["mastercorporate", "corporate"].includes(req.session.user.type) ? [req.session.user.id] : []),
|
||||
...(!!corporate ? [corporate.id] : []),
|
||||
],
|
||||
createdBy: req.session.user.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import useTrainingContentStore from "@/stores/trainingContentStore";
|
||||
import {Assignment} from "@/interfaces/results";
|
||||
import {getUsers} from "@/utils/users.be";
|
||||
import {getAssignments, getAssignmentsByAssigner} from "@/utils/assignments.be";
|
||||
import useGradingSystem from "@/hooks/useGrading";
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
const user = req.session.user;
|
||||
@@ -76,6 +77,7 @@ export default function History({user, users, assignments}: Props) {
|
||||
const [filter, setFilter] = useState<Filter>();
|
||||
|
||||
const {data: stats, isLoading: isStatsLoading} = useFilterRecordsByUser<Stat[]>(statsUserId || user?.id);
|
||||
const {gradingSystem} = useGradingSystem();
|
||||
|
||||
const setExams = useExamStore((state) => state.setExams);
|
||||
const setShowSolutions = useExamStore((state) => state.setShowSolutions);
|
||||
@@ -185,6 +187,7 @@ export default function History({user, users, assignments}: Props) {
|
||||
setSelectedTrainingExams={setSelectedTrainingExams}
|
||||
maxTrainingExams={MAX_TRAINING_EXAMS}
|
||||
setExams={setExams}
|
||||
gradingSystem={gradingSystem?.steps}
|
||||
setShowSolutions={setShowSolutions}
|
||||
setUserSolutions={setUserSolutions}
|
||||
setSelectedModules={setSelectedModules}
|
||||
|
||||
@@ -3,6 +3,8 @@ import {shuffle} from "lodash";
|
||||
import {Difficulty, Exam, InstructorGender, SpeakingExam, Variant, WritingExam} from "@/interfaces/exam";
|
||||
import {DeveloperUser, Stat, StudentUser, User} from "@/interfaces/user";
|
||||
import {Module} from "@/interfaces";
|
||||
import {getCorporateUser} from "@/resources/user";
|
||||
import {getUserCorporate} from "./groups.be";
|
||||
|
||||
export const getExams = async (
|
||||
db: Firestore,
|
||||
@@ -17,18 +19,21 @@ export const getExams = async (
|
||||
): Promise<Exam[]> => {
|
||||
const moduleRef = collection(db, module);
|
||||
|
||||
const q = query(moduleRef, and(where("isDiagnostic", "==", false), where("private", "!=", true)));
|
||||
const q = query(moduleRef, where("isDiagnostic", "==", false));
|
||||
const snapshot = await getDocs(q);
|
||||
|
||||
const allExams = shuffle(
|
||||
snapshot.docs.map((doc) => ({
|
||||
id: doc.id,
|
||||
...doc.data(),
|
||||
module,
|
||||
})),
|
||||
) as Exam[];
|
||||
const allExams = (
|
||||
shuffle(
|
||||
snapshot.docs.map((doc) => ({
|
||||
id: doc.id,
|
||||
...doc.data(),
|
||||
module,
|
||||
})),
|
||||
) as Exam[]
|
||||
).filter((x) => !x.private);
|
||||
|
||||
let exams: Exam[] = filterByVariant(allExams, variant);
|
||||
let exams: Exam[] = await filterByOwners(allExams, userId);
|
||||
exams = filterByVariant(exams, variant);
|
||||
exams = filterByInstructorGender(exams, instructorGender);
|
||||
exams = await filterByDifficulty(db, exams, module, userId);
|
||||
exams = await filterByPreference(db, exams, module, userId);
|
||||
@@ -60,6 +65,20 @@ const filterByVariant = (exams: Exam[], variant?: Variant) => {
|
||||
return filtered.length > 0 ? filtered : exams;
|
||||
};
|
||||
|
||||
const filterByOwners = async (exams: Exam[], userID?: string) => {
|
||||
if (!userID) return exams.filter((x) => !x.owners || x.owners.length === 0);
|
||||
return await Promise.all(
|
||||
exams.filter(async (x) => {
|
||||
if (!x.owners) return true;
|
||||
if (x.owners.length === 0) return true;
|
||||
if (x.owners.includes(userID)) return true;
|
||||
|
||||
const corporate = await getUserCorporate(userID);
|
||||
return !corporate ? false : x.owners.includes(corporate.id);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const filterByDifficulty = async (db: Firestore, exams: Exam[], module: Module, userID?: string) => {
|
||||
if (!userID) return exams;
|
||||
const userRef = await getDoc(doc(db, "users", userID));
|
||||
|
||||
@@ -35,6 +35,7 @@ export const updateExpiryDateOnGroup = async (participantID: string, corporateID
|
||||
|
||||
export const getUserCorporate = async (id: string) => {
|
||||
const user = await getUser(id);
|
||||
if (["admin", "developer"].includes(user.type)) return undefined;
|
||||
if (user.type === "mastercorporate") return user;
|
||||
|
||||
const groups = await getParticipantGroups(id);
|
||||
|
||||
Reference in New Issue
Block a user