Fix student performance freeze and search users in create entities
TODO: pagination in student performance freeze
This commit is contained in:
@@ -114,5 +114,6 @@
|
||||
"husky": "^8.0.3",
|
||||
"postcss": "^8.4.21",
|
||||
"tailwindcss": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
@@ -7,9 +7,17 @@ import {createColumnHelper} from "@tanstack/react-table";
|
||||
import Checkbox from "@/components/Low/Checkbox";
|
||||
import Table from "@/components/High/Table";
|
||||
|
||||
type StudentPerformanceItem = StudentUser & {entitiesLabel: string; group: string};
|
||||
type StudentPerformanceItem = StudentUser & {
|
||||
entitiesLabel: string;
|
||||
group: string;
|
||||
userStats: Stat[];
|
||||
};
|
||||
|
||||
const StudentPerformanceList = ({items = [], stats}: {items: StudentPerformanceItem[]; stats: Stat[]}) => {
|
||||
const StudentPerformanceList = ({
|
||||
items = [],
|
||||
}: {
|
||||
items: StudentPerformanceItem[];
|
||||
}) => {
|
||||
const [isShowingAmount, setIsShowingAmount] = useState(false);
|
||||
|
||||
const columnHelper = createColumnHelper<StudentPerformanceItem>();
|
||||
@@ -40,46 +48,86 @@ const StudentPerformanceList = ({items = [], stats}: {items: StudentPerformanceI
|
||||
cell: (info) =>
|
||||
!isShowingAmount
|
||||
? info.getValue() || 0
|
||||
: `${Object.keys(groupByExam(stats.filter((x) => x.module === "reading" && x.user === info.row.original.id))).length} exams`,
|
||||
: `${
|
||||
Object.keys(
|
||||
groupByExam(
|
||||
info.row.original.userStats.filter(
|
||||
(x) => x.module === "reading"
|
||||
)
|
||||
)
|
||||
).length
|
||||
} exams`,
|
||||
}),
|
||||
columnHelper.accessor("levels.listening", {
|
||||
header: "Listening",
|
||||
cell: (info) =>
|
||||
!isShowingAmount
|
||||
? info.getValue() || 0
|
||||
: `${Object.keys(groupByExam(stats.filter((x) => x.module === "listening" && x.user === info.row.original.id))).length} exams`,
|
||||
: `${
|
||||
Object.keys(
|
||||
groupByExam(
|
||||
info.row.original.userStats.filter(
|
||||
(x) => x.module === "listening"
|
||||
)
|
||||
)
|
||||
).length
|
||||
} exams`,
|
||||
}),
|
||||
columnHelper.accessor("levels.writing", {
|
||||
header: "Writing",
|
||||
cell: (info) =>
|
||||
!isShowingAmount
|
||||
? info.getValue() || 0
|
||||
: `${Object.keys(groupByExam(stats.filter((x) => x.module === "writing" && x.user === info.row.original.id))).length} exams`,
|
||||
: `${
|
||||
Object.keys(
|
||||
groupByExam(
|
||||
info.row.original.userStats.filter(
|
||||
(x) => x.module === "writing"
|
||||
)
|
||||
)
|
||||
).length
|
||||
} exams`,
|
||||
}),
|
||||
columnHelper.accessor("levels.speaking", {
|
||||
header: "Speaking",
|
||||
cell: (info) =>
|
||||
!isShowingAmount
|
||||
? info.getValue() || 0
|
||||
: `${Object.keys(groupByExam(stats.filter((x) => x.module === "speaking" && x.user === info.row.original.id))).length} exams`,
|
||||
: `${
|
||||
Object.keys(
|
||||
groupByExam(
|
||||
info.row.original.userStats.filter(
|
||||
(x) => x.module === "speaking"
|
||||
)
|
||||
)
|
||||
).length
|
||||
} exams`,
|
||||
}),
|
||||
columnHelper.accessor("levels.level", {
|
||||
header: "Level",
|
||||
cell: (info) =>
|
||||
!isShowingAmount
|
||||
? info.getValue() || 0
|
||||
: `${Object.keys(groupByExam(stats.filter((x) => x.module === "level" && x.user === info.row.original.id))).length} exams`,
|
||||
: `${
|
||||
Object.keys(
|
||||
groupByExam(
|
||||
info.row.original.userStats.filter(
|
||||
(x) => x.module === "level"
|
||||
)
|
||||
)
|
||||
).length
|
||||
} exams`,
|
||||
}),
|
||||
columnHelper.accessor("levels", {
|
||||
columnHelper.accessor("userStats", {
|
||||
id: "overall_level",
|
||||
header: "Overall",
|
||||
cell: (info) =>
|
||||
!isShowingAmount
|
||||
? averageLevelCalculator(
|
||||
items,
|
||||
stats.filter((x) => x.user === info.row.original.id),
|
||||
info.row.original.focus,
|
||||
info.getValue()
|
||||
).toFixed(1)
|
||||
: `${Object.keys(groupByExam(stats.filter((x) => x.user === info.row.original.id))).length} exams`,
|
||||
: `${Object.keys(groupByExam(info.getValue())).length} exams`,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -91,17 +139,17 @@ const StudentPerformanceList = ({items = [], stats}: {items: StudentPerformanceI
|
||||
<Table<StudentPerformanceItem>
|
||||
data={items.sort(
|
||||
(a, b) =>
|
||||
averageLevelCalculator(
|
||||
items,
|
||||
stats.filter((x) => x.user === b.id),
|
||||
) -
|
||||
averageLevelCalculator(
|
||||
items,
|
||||
stats.filter((x) => x.user === a.id),
|
||||
),
|
||||
averageLevelCalculator(b.focus, b.userStats) -
|
||||
averageLevelCalculator(a.focus, a.userStats)
|
||||
)}
|
||||
columns={columns}
|
||||
searchFields={[["name"], ["email"], ["studentID"], ["entitiesLabel"], ["group"]]}
|
||||
searchFields={[
|
||||
["name"],
|
||||
["email"],
|
||||
["studentID"],
|
||||
["entitiesLabel"],
|
||||
["group"],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function Home({ user, users }: Props) {
|
||||
const [licenses, setLicenses] = useState(0);
|
||||
|
||||
const { rows, renderSearch } = useListSearch<User>(
|
||||
[["name"], ["corporateInformation", "companyInformation", "name"]],
|
||||
[["name"], ["email"], ["corporateInformation", "companyInformation", "name"]],
|
||||
users
|
||||
);
|
||||
const { items, renderMinimal } = usePagination<User>(rows, 16);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
|
||||
import { BsChevronLeft } from "react-icons/bs";
|
||||
import { mapBy, serialize } from "@/utils";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import { getEntitiesUsers, getUsers } from "@/utils/users.be";
|
||||
import { getUsersWithStats } from "@/utils/users.be";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { checkAccess, findAllowedEntities } from "@/utils/permissions";
|
||||
import { getEntitiesWithRoles } from "@/utils/entities.be";
|
||||
@@ -33,8 +33,32 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
if (allowedEntities.length === 0) return redirect("/");
|
||||
|
||||
const students = await (checkAccess(user, ["admin", "developer"])
|
||||
? getUsers({ type: "student" })
|
||||
: getEntitiesUsers(mapBy(allowedEntities, "id"), { type: "student" }));
|
||||
? getUsersWithStats(
|
||||
{ type: "student" },
|
||||
{
|
||||
id: 1,
|
||||
entities: 1,
|
||||
focus: 1,
|
||||
email: 1,
|
||||
name: 1,
|
||||
levels: 1,
|
||||
userStats: 1,
|
||||
studentID: 1,
|
||||
}
|
||||
)
|
||||
: getUsersWithStats(
|
||||
{ type: "student", "entities.id": { in: mapBy(entities, "id") } },
|
||||
{
|
||||
id: 1,
|
||||
entities: 1,
|
||||
focus: 1,
|
||||
email: 1,
|
||||
name: 1,
|
||||
levels: 1,
|
||||
userStats: 1,
|
||||
studentID: 1,
|
||||
}
|
||||
));
|
||||
const groups = await getParticipantsGroups(mapBy(students, "id"));
|
||||
|
||||
return {
|
||||
@@ -44,14 +68,12 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
students: StudentUser[];
|
||||
students: (StudentUser & { userStats: Stat[] })[];
|
||||
entities: Entity[];
|
||||
groups: Group[];
|
||||
}
|
||||
|
||||
const StudentPerformance = ({ user, students, entities, groups }: Props) => {
|
||||
const { data: stats } = useFilterRecordsByUser<Stat[]>();
|
||||
|
||||
const StudentPerformance = ({ students, entities, groups }: Props) => {
|
||||
const router = useRouter();
|
||||
|
||||
const performanceStudents = students.map((u) => ({
|
||||
@@ -76,7 +98,6 @@ const StudentPerformance = ({ user, students, entities, groups }: Props) => {
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<ToastContainer />
|
||||
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
@@ -91,7 +112,7 @@ const StudentPerformance = ({ user, students, entities, groups }: Props) => {
|
||||
Student Performance ({students.length})
|
||||
</h2>
|
||||
</div>
|
||||
<StudentPerformanceList items={performanceStudents} stats={stats} />
|
||||
<StudentPerformanceList items={performanceStudents} />
|
||||
</>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -70,9 +70,9 @@ export const getExamById = async (module: Module, id: string): Promise<Exam | un
|
||||
|
||||
export const defaultExamUserSolutions = (exam: Exam) => {
|
||||
if (exam.module === "reading" || exam.module === "listening" || exam.module === "level")
|
||||
return exam.parts.flatMap((x) => x.exercises).map((x) => defaultUserSolutions(x, exam));
|
||||
return (exam.parts.flatMap((x) => x.exercises) ?? []).map((x) => defaultUserSolutions(x, exam));
|
||||
|
||||
return exam.exercises.map((x) => defaultUserSolutions(x, exam));
|
||||
return (exam.exercises ?? []).map((x) => defaultUserSolutions(x, exam));
|
||||
};
|
||||
|
||||
export const defaultUserSolutions = (exercise: Exercise, exam: Exam): UserSolution => {
|
||||
|
||||
@@ -193,17 +193,18 @@ export const getGradingLabel = (score: number, grading: Step[]) => {
|
||||
return "N/A";
|
||||
};
|
||||
|
||||
export const averageLevelCalculator = (users: User[], studentStats: Stat[]) => {
|
||||
const formattedStats = studentStats
|
||||
export const averageLevelCalculator = (focus: Type, studentStats: Stat[]) => {
|
||||
/* const formattedStats = studentStats
|
||||
.map((s) => ({
|
||||
focus: users.find((u) => u.id === s.user)?.focus,
|
||||
focus: focus,
|
||||
score: s.score,
|
||||
module: s.module,
|
||||
}))
|
||||
.filter((f) => !!f.focus);
|
||||
const bandScores = formattedStats.map((s) => ({
|
||||
.filter((f) => !!f.focus); */
|
||||
|
||||
const bandScores = studentStats.map((s) => ({
|
||||
module: s.module,
|
||||
level: calculateBandScore(s.score.correct, s.score.total, s.module, s.focus!),
|
||||
level: calculateBandScore(s.score.correct, s.score.total, s.module, focus),
|
||||
}));
|
||||
|
||||
const levels: { [key in Module]: number } = {
|
||||
|
||||
@@ -20,6 +20,15 @@ export async function getUsers(filter?: object, limit = 0, sort = {}, projection
|
||||
.toArray();
|
||||
}
|
||||
|
||||
export async function getUsersWithStats(filter?: object, projection = {}, limit = 0, sort = {}) {
|
||||
return await db
|
||||
.collection("usersWithStats")
|
||||
.find<User>(filter || {}, { projection: { _id: 0, ...projection } })
|
||||
.limit(limit)
|
||||
.sort(sort)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
export async function searchUsers(searchInput?: string, limit = 50, page = 0, sort: object = { "name": 1 }, projection = {}, filter?: object) {
|
||||
const compoundFilter = {
|
||||
"compound": {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
, "scripts/updatePrivateFieldExams.js" ],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user