Merge branch 'feature/ExamGenRework' of https://bitbucket.org/ecropdev/ielts-ui into feature/ExamGenRework
This commit is contained in:
20626
package-lock.json
generated
20626
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
229
package.json
229
package.json
@@ -1,117 +1,116 @@
|
||||
{
|
||||
"name": "next-wind",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@beam-australia/react-env": "^3.1.1",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@firebase/util": "^1.9.7",
|
||||
"@headlessui/react": "^2.1.2",
|
||||
"@mdi/js": "^7.1.96",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@paypal/paypal-js": "^7.1.0",
|
||||
"@paypal/react-paypal-js": "^8.1.3",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@react-pdf/renderer": "^3.1.14",
|
||||
"@react-spring/web": "^9.7.4",
|
||||
"@tanstack/react-table": "^8.10.1",
|
||||
"@types/node": "18.13.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"axios": "^1",
|
||||
"axios-cache-interceptor": "^1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chart.js": "^4.2.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"countries-list": "^3.0.1",
|
||||
"country-codes-list": "^1.6.11",
|
||||
"currency-symbol-map": "^5.1.0",
|
||||
"daisyui": "^3.1.5",
|
||||
"eslint": "8.33.0",
|
||||
"eslint-config-next": "13.1.6",
|
||||
"exceljs": "^4.4.0",
|
||||
"express-handlebars": "^7.1.2",
|
||||
"firebase": "9.19.1",
|
||||
"firebase-admin": "^11.10.1",
|
||||
"firebase-scrypt": "^2.2.0",
|
||||
"formidable": "^3.5.0",
|
||||
"formidable-serverless": "^1.1.1",
|
||||
"framer-motion": "^9.0.2",
|
||||
"howler": "^2.2.4",
|
||||
"immer": "^10.1.1",
|
||||
"iron-session": "^6.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.44",
|
||||
"mongodb": "^6.8.1",
|
||||
"next": "^14.2.5",
|
||||
"nodemailer": "^6.9.5",
|
||||
"nodemailer-express-handlebars": "^6.1.0",
|
||||
"primeicons": "^6.0.1",
|
||||
"primereact": "^9.2.3",
|
||||
"qrcode": "^1.5.3",
|
||||
"random-words": "^2.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-csv": "^2.2.2",
|
||||
"react-currency-input-field": "^3.6.12",
|
||||
"react-datepicker": "^4.18.0",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-firebase-hooks": "^5.1.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-lineto": "^3.3.0",
|
||||
"react-media-recorder": "1.6.5",
|
||||
"react-phone-number-input": "^3.3.6",
|
||||
"react-player": "^2.12.0",
|
||||
"react-select": "^5.7.5",
|
||||
"react-string-replace": "^1.1.0",
|
||||
"react-toastify": "^9.1.2",
|
||||
"react-tooltip": "^5.27.1",
|
||||
"react-xarrows": "^2.0.2",
|
||||
"read-excel-file": "^5.7.1",
|
||||
"short-unique-id": "5.0.2",
|
||||
"stripe": "^13.10.0",
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "4.9.5",
|
||||
"use-file-picker": "^2.1.0",
|
||||
"uuid": "^9.0.0",
|
||||
"wavesurfer.js": "^6.6.4",
|
||||
"zustand": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@simbathesailor/use-what-changed": "^2.0.0",
|
||||
"@types/blob-stream": "^0.1.33",
|
||||
"@types/formidable": "^3.4.0",
|
||||
"@types/howler": "^2.2.11",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/nodemailer": "^6.4.11",
|
||||
"@types/nodemailer-express-handlebars": "^4.0.3",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-csv": "^1.1.10",
|
||||
"@types/react-datepicker": "^4.15.1",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@types/wavesurfer.js": "^6.0.6",
|
||||
"@welldone-software/why-did-you-render": "^8.0.3",
|
||||
"@wixc3/react-board": "^2.2.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"husky": "^8.0.3",
|
||||
"postcss": "^8.4.21",
|
||||
"tailwindcss": "^3.2.4"
|
||||
}
|
||||
"name": "next-wind",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@beam-australia/react-env": "^3.1.1",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@firebase/util": "^1.9.7",
|
||||
"@headlessui/react": "^2.1.2",
|
||||
"@mdi/js": "^7.1.96",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@paypal/paypal-js": "^7.1.0",
|
||||
"@paypal/react-paypal-js": "^8.1.3",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@react-pdf/renderer": "^3.1.14",
|
||||
"@react-spring/web": "^9.7.4",
|
||||
"@tanstack/react-table": "^8.10.1",
|
||||
"@types/node": "18.13.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"axios": "^1",
|
||||
"axios-cache-interceptor": "^1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chart.js": "^4.2.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"countries-list": "^3.0.1",
|
||||
"country-codes-list": "^1.6.11",
|
||||
"currency-symbol-map": "^5.1.0",
|
||||
"daisyui": "^3.1.5",
|
||||
"eslint": "8.33.0",
|
||||
"eslint-config-next": "13.1.6",
|
||||
"exceljs": "^4.4.0",
|
||||
"express-handlebars": "^7.1.2",
|
||||
"firebase": "9.19.1",
|
||||
"firebase-admin": "^11.10.1",
|
||||
"firebase-scrypt": "^2.2.0",
|
||||
"formidable": "^3.5.0",
|
||||
"formidable-serverless": "^1.1.1",
|
||||
"framer-motion": "^9.0.2",
|
||||
"howler": "^2.2.4",
|
||||
"immer": "^10.1.1",
|
||||
"iron-session": "^6.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.44",
|
||||
"mongodb": "^6.8.1",
|
||||
"next": "^14.2.5",
|
||||
"nodemailer": "^6.9.5",
|
||||
"nodemailer-express-handlebars": "^6.1.0",
|
||||
"primeicons": "^6.0.1",
|
||||
"primereact": "^9.2.3",
|
||||
"qrcode": "^1.5.3",
|
||||
"random-words": "^2.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-csv": "^2.2.2",
|
||||
"react-currency-input-field": "^3.6.12",
|
||||
"react-datepicker": "^4.18.0",
|
||||
"react-diff-viewer": "^3.1.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-firebase-hooks": "^5.1.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-lineto": "^3.3.0",
|
||||
"react-media-recorder": "1.6.5",
|
||||
"react-phone-number-input": "^3.3.6",
|
||||
"react-player": "^2.12.0",
|
||||
"react-select": "^5.7.5",
|
||||
"react-string-replace": "^1.1.0",
|
||||
"react-toastify": "^9.1.2",
|
||||
"react-tooltip": "^5.27.1",
|
||||
"react-xarrows": "^2.0.2",
|
||||
"read-excel-file": "^5.7.1",
|
||||
"short-unique-id": "5.0.2",
|
||||
"stripe": "^13.10.0",
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "4.9.5",
|
||||
"use-file-picker": "^2.1.0",
|
||||
"uuid": "^9.0.0",
|
||||
"wavesurfer.js": "^6.6.4",
|
||||
"zustand": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@simbathesailor/use-what-changed": "^2.0.0",
|
||||
"@types/blob-stream": "^0.1.33",
|
||||
"@types/formidable": "^3.4.0",
|
||||
"@types/howler": "^2.2.11",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/nodemailer": "^6.4.11",
|
||||
"@types/nodemailer-express-handlebars": "^4.0.3",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-csv": "^1.1.10",
|
||||
"@types/react-datepicker": "^4.15.1",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@types/wavesurfer.js": "^6.0.6",
|
||||
"@wixc3/react-board": "^2.2.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"husky": "^8.0.3",
|
||||
"postcss": "^8.4.21",
|
||||
"tailwindcss": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//import "@/utils/wdyr";
|
||||
|
||||
import { FillBlanksExercise, FillBlanksMCOption } from "@/interfaces/exam";
|
||||
import clsx from "clsx";
|
||||
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
@@ -99,7 +97,7 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
||||
shuffleMaps,
|
||||
isPractice
|
||||
}));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, answers, type, isPractice, shuffleMaps, calculateScore]);
|
||||
|
||||
const [openDropdownId, setOpenDropdownId] = useState<string | null>(null);
|
||||
|
||||
@@ -2,9 +2,9 @@ import React from "react";
|
||||
import { BsClock, BsXCircle } from "react-icons/bs";
|
||||
import clsx from "clsx";
|
||||
import { Stat, User } from "@/interfaces/user";
|
||||
import { Module, Step } from "@/interfaces";
|
||||
import { Grading, Module, Step } from "@/interfaces";
|
||||
import ai_usage from "@/utils/ai.detection";
|
||||
import { calculateBandScore } from "@/utils/score";
|
||||
import { calculateBandScore, getGradingLabel } from "@/utils/score";
|
||||
import moment from "moment";
|
||||
import { Assignment } from "@/interfaces/results";
|
||||
import { uuidv4 } from "@firebase/util";
|
||||
@@ -15,6 +15,7 @@ import { getExamById } from "@/utils/exams";
|
||||
import { Exam, UserSolution } from "@/interfaces/exam";
|
||||
import ModuleBadge from "../ModuleBadge";
|
||||
import useExamStore from "@/stores/exam";
|
||||
import { findBy } from "@/utils";
|
||||
|
||||
const formatTimestamp = (timestamp: string | number) => {
|
||||
const time = typeof timestamp === "string" ? parseInt(timestamp) : timestamp;
|
||||
@@ -70,6 +71,7 @@ const aggregateScoresByModule = (stats: Stat[]): { module: Module; total: number
|
||||
interface StatsGridItemProps {
|
||||
width?: string | undefined;
|
||||
height?: string | undefined;
|
||||
gradingSystems: Grading[]
|
||||
examNumber?: number | undefined;
|
||||
stats: Stat[];
|
||||
timestamp: string | number;
|
||||
@@ -88,6 +90,7 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
||||
timestamp,
|
||||
user,
|
||||
assignments,
|
||||
gradingSystems,
|
||||
users,
|
||||
training,
|
||||
selectedTrainingExams,
|
||||
@@ -182,9 +185,34 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
||||
return true;
|
||||
};
|
||||
|
||||
const levelAverage = () =>
|
||||
aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length
|
||||
|
||||
const renderLevelScore = () => {
|
||||
const defaultLevelScore = levelAverage().toFixed(1)
|
||||
if (!stats.every(s => s.module === "level")) return defaultLevelScore
|
||||
if (gradingSystems.length === 0) return defaultLevelScore
|
||||
|
||||
const score = {
|
||||
correct: stats.reduce((acc, curr) => acc + curr.score.correct, 0),
|
||||
total: stats.reduce((acc, curr) => acc + curr.score.total, 0)
|
||||
}
|
||||
|
||||
const level: number = calculateBandScore(score.correct, score.total, "level", user.focus);
|
||||
|
||||
if (!!assignment) {
|
||||
const gradingSystem = findBy(gradingSystems, 'entity', assignment.entity)
|
||||
if (!gradingSystem) return defaultLevelScore
|
||||
|
||||
return getGradingLabel(level, gradingSystem.steps)
|
||||
}
|
||||
|
||||
return getGradingLabel(level, gradingSystems[0].steps)
|
||||
}
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<div className="w-full flex justify-between -md:items-center 2xl:items-center">
|
||||
<div className="w-full flex justify-between">
|
||||
<div className="flex flex-col md:gap-1 -md:gap-2 2xl:gap-2">
|
||||
<span className="font-medium">{formatTimestamp(timestamp)}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -202,12 +230,10 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-row gap-2">
|
||||
{!!assignment && (assignment.released || assignment.released === undefined) && (
|
||||
{((!!assignment && (assignment.released || assignment.released === undefined)) || !assignment) && (
|
||||
<span className={textColor}>
|
||||
Level{" "}
|
||||
{(
|
||||
aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length
|
||||
).toFixed(1)}
|
||||
Level{' '}
|
||||
{renderLevelScore()}
|
||||
</span>
|
||||
)}
|
||||
{shouldRenderPDFIcon() && renderPdfIcon(session, textColor, textColor)}
|
||||
|
||||
@@ -18,10 +18,7 @@ import {
|
||||
BsHeadphones,
|
||||
BsMegaphone,
|
||||
BsPen,
|
||||
BsShareFill,
|
||||
} from "react-icons/bs";
|
||||
import { LevelScore } from "@/constants/ielts";
|
||||
import { getLevelScore } from "@/utils/score";
|
||||
import { capitalize } from "lodash";
|
||||
import Modal from "@/components/Modal";
|
||||
import { UserSolution } from "@/interfaces/exam";
|
||||
@@ -67,6 +64,7 @@ export default function Finish({ user, practiceScores, scores, modules, informat
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => setSelectedScore(scores.find((x) => x.module === selectedModule)!), [scores, selectedModule]);
|
||||
useEffect(() => setSelectedPracticeScore(practiceScores.find((x) => x.module === selectedModule)!), [practiceScores, selectedModule]);
|
||||
|
||||
const moduleColors: { [key in Module]: { progress: string; inner: string } } = {
|
||||
reading: {
|
||||
@@ -289,7 +287,7 @@ export default function Finish({ user, practiceScores, scores, modules, informat
|
||||
<div className="bg-mti-green mt-1 h-3 min-h-[0.75rem] w-3 min-w-[0.75rem] rounded-full" />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-mti-green">
|
||||
{selectedPracticeScore.correct} / {selectedScore.total}
|
||||
{selectedPracticeScore.correct} / {selectedPracticeScore.total}
|
||||
</span>
|
||||
<span className="text-lg whitespace-nowrap">Practice Questions</span>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
//import "@/utils/wdyr";
|
||||
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
@@ -112,4 +111,4 @@ const Page: React.FC<Props> = ({ user, assignment, exams = [], destinationURL =
|
||||
}
|
||||
|
||||
//Page.whyDidYouRender = true;
|
||||
export default Page;
|
||||
export default Page;
|
||||
|
||||
@@ -26,7 +26,7 @@ import { mapBy, redirect, serialize } from "@/utils";
|
||||
import { getEntitiesWithRoles } from "@/utils/entities.be";
|
||||
import { checkAccess } from "@/utils/permissions";
|
||||
import { getGroups, getGroupsByEntities } from "@/utils/groups.be";
|
||||
import { getGradingSystemByEntity } from "@/utils/grading.be";
|
||||
import { getGradingSystemByEntities, getGradingSystemByEntity } from "@/utils/grading.be";
|
||||
import { Grading } from "@/interfaces";
|
||||
import { EntityWithRoles } from "@/interfaces/entity";
|
||||
import CardList from "@/components/High/CardList";
|
||||
@@ -43,9 +43,10 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
const entities = await getEntitiesWithRoles(checkAccess(user, ["admin", "developer"]) ? undefined : entityIDs)
|
||||
const users = await (checkAccess(user, ["admin", "developer"]) ? getUsers() : getEntitiesUsers(mapBy(entities, 'id')))
|
||||
const assignments = await (checkAccess(user, ["admin", "developer"]) ? getAssignments() : getEntitiesAssignments(mapBy(entities, 'id')))
|
||||
const gradingSystems = await getGradingSystemByEntities(mapBy(entities, 'id'))
|
||||
|
||||
return {
|
||||
props: serialize({ user, users, assignments, entities }),
|
||||
props: serialize({ user, users, assignments, entities, gradingSystems }),
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
@@ -56,11 +57,12 @@ interface Props {
|
||||
users: User[];
|
||||
assignments: Assignment[];
|
||||
entities: EntityWithRoles[]
|
||||
gradingSystems: Grading[]
|
||||
}
|
||||
|
||||
const MAX_TRAINING_EXAMS = 10;
|
||||
|
||||
export default function History({ user, users, assignments, entities }: Props) {
|
||||
export default function History({ user, users, assignments, entities, gradingSystems }: Props) {
|
||||
const router = useRouter();
|
||||
const [statsUserId, setStatsUserId, training, setTraining] = useRecordStore((state) => [
|
||||
state.selectedUser,
|
||||
@@ -158,6 +160,7 @@ export default function History({ user, users, assignments, entities }: Props) {
|
||||
<StatsGridItem
|
||||
key={uuidv4()}
|
||||
stats={dateStats}
|
||||
gradingSystems={gradingSystems}
|
||||
timestamp={timestamp}
|
||||
user={user}
|
||||
assignments={assignments}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {useRouter} from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import axios from "axios";
|
||||
import {Tab} from "@headlessui/react";
|
||||
import {AiOutlineFileSearch} from "react-icons/ai";
|
||||
import {MdOutlinePlaylistAddCheckCircle, MdOutlineSelfImprovement} from "react-icons/md";
|
||||
import {BsChatLeftDots} from "react-icons/bs";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { AiOutlineFileSearch } from "react-icons/ai";
|
||||
import { MdOutlinePlaylistAddCheckCircle, MdOutlineSelfImprovement } from "react-icons/md";
|
||||
import { BsChatLeftDots } from "react-icons/bs";
|
||||
import Button from "@/components/Low/Button";
|
||||
import clsx from "clsx";
|
||||
import Exercise from "@/training/Exercise";
|
||||
import TrainingScore from "@/training/TrainingScore";
|
||||
import {ITrainingContent, ITrainingTip} from "@/training/TrainingInterfaces";
|
||||
import { ITrainingContent, ITrainingTip } from "@/training/TrainingInterfaces";
|
||||
import formatTip from "@/training/FormatTip";
|
||||
import {Stat, User} from "@/interfaces/user";
|
||||
import { Stat, User } from "@/interfaces/user";
|
||||
import Head from "next/head";
|
||||
import Layout from "@/components/High/Layout";
|
||||
import {ToastContainer} from "react-toastify";
|
||||
import {withIronSessionSsr} from "iron-session/next";
|
||||
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||
import {sessionOptions} from "@/lib/session";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import qs from "qs";
|
||||
import StatsGridItem from "@/components/Medium/StatGridItem";
|
||||
import useExamStore from "@/stores/exam";
|
||||
import {usePDFDownload} from "@/hooks/usePDFDownload";
|
||||
import { usePDFDownload } from "@/hooks/usePDFDownload";
|
||||
import useAssignments from "@/hooks/useAssignments";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import Dropdown from "@/components/Dropdown";
|
||||
import InfiniteCarousel from "@/components/InfiniteCarousel";
|
||||
import {LuExternalLink} from "react-icons/lu";
|
||||
import {uniqBy} from "lodash";
|
||||
import {getExamById} from "@/utils/exams";
|
||||
import {sortByModule} from "@/utils/moduleUtils";
|
||||
import { LuExternalLink } from "react-icons/lu";
|
||||
import { uniqBy } from "lodash";
|
||||
import { getExamById } from "@/utils/exams";
|
||||
import { sortByModule } from "@/utils/moduleUtils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { redirect, serialize } from "@/utils";
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
const user = await requestUser(req, res)
|
||||
if (!user) return redirect("/login")
|
||||
|
||||
if (shouldRedirectHome(user)) redirect("/")
|
||||
|
||||
return {
|
||||
props: serialize({user}),
|
||||
props: serialize({ user }),
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
const TrainingContent: React.FC<{user: User}> = ({user}) => {
|
||||
const TrainingContent: React.FC<{ user: User }> = ({ user }) => {
|
||||
const renderPdfIcon = usePDFDownload("stats");
|
||||
|
||||
const dispatch = useExamStore((s) => s.dispatch);
|
||||
@@ -53,11 +53,11 @@ const TrainingContent: React.FC<{user: User}> = ({user}) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [trainingTips, setTrainingTips] = useState<ITrainingTip[]>([]);
|
||||
const [currentTipIndex, setCurrentTipIndex] = useState(0);
|
||||
const {assignments} = useAssignments({});
|
||||
const {users} = useUsers();
|
||||
const { assignments } = useAssignments({});
|
||||
const { users } = useUsers();
|
||||
|
||||
const router = useRouter();
|
||||
const {id} = router.query;
|
||||
const { id } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTrainingContent = async () => {
|
||||
@@ -78,14 +78,14 @@ const TrainingContent: React.FC<{user: User}> = ({user}) => {
|
||||
return statResponse.data;
|
||||
}),
|
||||
);
|
||||
return {...exam, stats};
|
||||
return { ...exam, stats };
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
const tips = await axios.get<ITrainingTip[]>("/api/training/walkthrough", {
|
||||
params: {ids: trainingContent.tip_ids},
|
||||
paramsSerializer: (params) => qs.stringify(params, {arrayFormat: "repeat"}),
|
||||
params: { ids: trainingContent.tip_ids },
|
||||
paramsSerializer: (params) => qs.stringify(params, { arrayFormat: "repeat" }),
|
||||
});
|
||||
|
||||
const processedTips = tips.data.map(formatTip);
|
||||
@@ -115,7 +115,7 @@ const TrainingContent: React.FC<{user: User}> = ({user}) => {
|
||||
return getExamById(stat.module, stat.exam);
|
||||
});
|
||||
|
||||
const {timeSpent, inactivity} = stats[0];
|
||||
const { timeSpent, inactivity } = stats[0];
|
||||
|
||||
Promise.all(examPromises).then((exams) => {
|
||||
if (exams.every((x) => !!x)) {
|
||||
@@ -172,6 +172,7 @@ const TrainingContent: React.FC<{user: User}> = ({user}) => {
|
||||
{trainingContent.exams.map((exam, examIndex) => (
|
||||
<StatsGridItem
|
||||
key={`exam-${examIndex}`}
|
||||
gradingSystems={[]}
|
||||
width="380px"
|
||||
height="150px"
|
||||
examNumber={examIndex + 1}
|
||||
@@ -241,7 +242,7 @@ const TrainingContent: React.FC<{user: User}> = ({user}) => {
|
||||
{trainingContent.weak_areas.map((x, index) => (
|
||||
<Tab
|
||||
key={index}
|
||||
className={({selected}) =>
|
||||
className={({ selected }) =>
|
||||
clsx(
|
||||
"text-[#53B2F9] pb-2 border-b-2",
|
||||
"focus:outline-none",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Stat} from "@/interfaces/user";
|
||||
import {capitalize, groupBy} from "lodash";
|
||||
import {convertCamelCaseToReadable} from "@/utils/string";
|
||||
import {UserSolution} from "@/interfaces/exam";
|
||||
import {Module} from "@/interfaces";
|
||||
import {MODULES} from "@/constants/ielts";
|
||||
import { Stat } from "@/interfaces/user";
|
||||
import { capitalize, groupBy } from "lodash";
|
||||
import { convertCamelCaseToReadable } from "@/utils/string";
|
||||
import { UserSolution } from "@/interfaces/exam";
|
||||
import { Module } from "@/interfaces";
|
||||
import { MODULES } from "@/constants/ielts";
|
||||
import moment from "moment";
|
||||
|
||||
export const timestampToMoment = (stat: Stat): moment.Moment => {
|
||||
@@ -16,15 +16,15 @@ export const totalExams = (stats: Stat[]): number => {
|
||||
};
|
||||
|
||||
export const averageScore = (stats: Stat[]): number => {
|
||||
const {correct, total} = stats.reduce(
|
||||
(acc, current) => ({correct: acc.correct + current.score.correct, total: acc.total + current.score.total}),
|
||||
{correct: 0, total: 0},
|
||||
const { correct, total } = stats.reduce(
|
||||
(acc, current) => ({ correct: acc.correct + current.score.correct, total: acc.total + current.score.total }),
|
||||
{ correct: 0, total: 0 },
|
||||
);
|
||||
return parseFloat(((correct / total) * 100).toFixed(2));
|
||||
};
|
||||
|
||||
export const formatModuleTotalStats = (stats: Stat[]): {label: string; value: number}[] => {
|
||||
const moduleSessions: {[key: string]: string[]} = {};
|
||||
export const formatModuleTotalStats = (stats: Stat[]): { label: string; value: number }[] => {
|
||||
const moduleSessions: { [key: string]: string[] } = {};
|
||||
|
||||
stats.forEach((stat) => {
|
||||
if (stat.module in moduleSessions) {
|
||||
@@ -43,7 +43,7 @@ export const formatModuleTotalStats = (stats: Stat[]): {label: string; value: nu
|
||||
};
|
||||
|
||||
export const totalExamsByModule = (stats: Stat[], module: Module): number => {
|
||||
const moduleSessions: {[key: string]: string[]} = {};
|
||||
const moduleSessions: { [key: string]: string[] } = {};
|
||||
|
||||
stats.forEach((stat) => {
|
||||
if (stat.module in moduleSessions) {
|
||||
@@ -58,8 +58,8 @@ export const totalExamsByModule = (stats: Stat[], module: Module): number => {
|
||||
return moduleSessions[module]?.length || 0;
|
||||
};
|
||||
|
||||
export const calculateModuleAverageScoreStats = (stats: Stat[]): {module: Module; value: number}[] => {
|
||||
const moduleScores: {[key: string]: {correct: number; total: number}} = {};
|
||||
export const calculateModuleAverageScoreStats = (stats: Stat[]): { module: Module; value: number }[] => {
|
||||
const moduleScores: { [key: string]: { correct: number; total: number } } = {};
|
||||
|
||||
stats.forEach((stat) => {
|
||||
if (stat.module in moduleScores) {
|
||||
@@ -82,11 +82,11 @@ export const calculateModuleAverageScoreStats = (stats: Stat[]): {module: Module
|
||||
});
|
||||
};
|
||||
|
||||
export const formatModuleAverageScoreStats = (stats: Stat[]): {label: string; value: number}[] => {
|
||||
return calculateModuleAverageScoreStats(stats).map((x) => ({label: capitalize(x.module), value: x.value}));
|
||||
export const formatModuleAverageScoreStats = (stats: Stat[]): { label: string; value: number }[] => {
|
||||
return calculateModuleAverageScoreStats(stats).map((x) => ({ label: capitalize(x.module), value: x.value }));
|
||||
};
|
||||
|
||||
export const formatExerciseTotalStats = (stats: Stat[]): {label: string; value: number}[] => {
|
||||
export const formatExerciseTotalStats = (stats: Stat[]): { label: string; value: number }[] => {
|
||||
const totalExercises = stats.map((stat) => ({
|
||||
label: convertCamelCaseToReadable(stat.type),
|
||||
value: stats.filter((x) => x.type === stat.type).length,
|
||||
@@ -95,8 +95,8 @@ export const formatExerciseTotalStats = (stats: Stat[]): {label: string; value:
|
||||
return totalExercises.filter((ex, index) => totalExercises.findIndex((x) => x.label === ex.label) === index);
|
||||
};
|
||||
|
||||
export const formatExerciseAverageScoreStats = (stats: Stat[]): {label: string; value: number}[] => {
|
||||
const typeScores: {[key: string]: {correct: number; total: number}} = {};
|
||||
export const formatExerciseAverageScoreStats = (stats: Stat[]): { label: string; value: number }[] => {
|
||||
const typeScores: { [key: string]: { correct: number; total: number } } = {};
|
||||
|
||||
stats.forEach((stat) => {
|
||||
if (stat.type in typeScores) {
|
||||
@@ -110,7 +110,7 @@ export const formatExerciseAverageScoreStats = (stats: Stat[]): {label: string;
|
||||
});
|
||||
|
||||
return Object.keys(typeScores).map((x) => {
|
||||
const {correct, total} = typeScores[x as keyof typeof typeScores];
|
||||
const { correct, total } = typeScores[x as keyof typeof typeScores];
|
||||
|
||||
return {
|
||||
label: convertCamelCaseToReadable(x),
|
||||
@@ -138,6 +138,7 @@ export const convertToUserSolutions = (stats: Stat[]): UserSolution[] => {
|
||||
solutions: stat.solutions,
|
||||
type: stat.type,
|
||||
module: stat.module,
|
||||
shuffleMaps: stat.shuffleMaps
|
||||
shuffleMaps: stat.shuffleMaps,
|
||||
isPractice: stat.isPractice
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/// <reference types="@welldone-software/why-did-you-render" />
|
||||
|
||||
import React from "react";
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const whyDidYouRender = require("@welldone-software/why-did-you-render");
|
||||
whyDidYouRender(React, {
|
||||
trackAllPureComponents: true,
|
||||
trackHooks: true,
|
||||
logOwnerReasons: true,
|
||||
collapseGroups: true,
|
||||
});
|
||||
}
|
||||
@@ -1,37 +1,43 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"downlevelIteration": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@/training/*": ["./src/components/TrainingContent/*"],
|
||||
"@/editor/*": ["./src/components/ExamEditor/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"downlevelIteration": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@/training/*": [
|
||||
"./src/components/TrainingContent/*"
|
||||
],
|
||||
"@/editor/*": [
|
||||
"./src/components/ExamEditor/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user