Added level export to excel
This commit is contained in:
@@ -18,6 +18,7 @@ import Checkbox from "@/components/Low/Checkbox";
|
|||||||
import { useListSearch } from "@/hooks/useListSearch";
|
import { useListSearch } from "@/hooks/useListSearch";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import Button from "@/components/Low/Button";
|
||||||
interface Props {
|
interface Props {
|
||||||
corporateUsers: User[];
|
corporateUsers: User[];
|
||||||
users: User[];
|
users: User[];
|
||||||
@@ -207,10 +208,11 @@ const MasterStatistical = (props: Props) => {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const { rows: filteredRows, renderSearch, text: searchText } = useListSearch(
|
const {
|
||||||
searchFilters,
|
rows: filteredRows,
|
||||||
tableResults
|
renderSearch,
|
||||||
);
|
text: searchText,
|
||||||
|
} = useListSearch(searchFilters, tableResults);
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: filteredRows,
|
data: filteredRows,
|
||||||
@@ -240,7 +242,6 @@ const MasterStatistical = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const triggerDownload = async () => {
|
const triggerDownload = async () => {
|
||||||
try {
|
try {
|
||||||
setDownloading(true);
|
setDownloading(true);
|
||||||
@@ -267,28 +268,9 @@ const MasterStatistical = (props: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderIcon = () => {
|
|
||||||
if (downloading) {
|
|
||||||
return (
|
|
||||||
<span className={`text-mti-gray-dim loading loading-infinity w-6`} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BsFileExcel
|
|
||||||
className={`text-mti-gray-dim text-2xl cursor-pointer`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
triggerDownload();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const consolidateResults = getStudentsConsolidateScore();
|
const consolidateResults = getStudentsConsolidateScore();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderIcon()}
|
|
||||||
<div className="flex flex-wrap gap-2 items-center text-center">
|
<div className="flex flex-wrap gap-2 items-center text-center">
|
||||||
<IconCard
|
<IconCard
|
||||||
Icon={BsBank}
|
Icon={BsBank}
|
||||||
@@ -353,6 +335,15 @@ const MasterStatistical = (props: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{renderSearch()}
|
{renderSearch()}
|
||||||
|
<div className="flex flex-col gap-3 justify-end">
|
||||||
|
<Button
|
||||||
|
className="max-w-[200px] h-[70px]"
|
||||||
|
variant="outline"
|
||||||
|
onClick={triggerDownload}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import { getSpecificUsers } from "@/utils/users.be";
|
|||||||
import { checkAccess } from "@/utils/permissions";
|
import { checkAccess } from "@/utils/permissions";
|
||||||
import { getAssignmentsForCorporates } from "@/utils/assignments.be";
|
import { getAssignmentsForCorporates } from "@/utils/assignments.be";
|
||||||
import { search } from "@/utils/search";
|
import { search } from "@/utils/search";
|
||||||
|
import { getGradingSystem } from "@/utils/grading.be";
|
||||||
|
import { Exam } from "@/interfaces/exam";
|
||||||
|
import { User } from "@/interfaces/user";
|
||||||
|
import { calculateBandScore, getGradingLabel } from "@/utils/score";
|
||||||
|
import { Module } from "@/interfaces";
|
||||||
const db = getFirestore(app);
|
const db = getFirestore(app);
|
||||||
|
|
||||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||||
@@ -25,6 +29,7 @@ interface TableData {
|
|||||||
date: moment.Moment;
|
date: moment.Moment;
|
||||||
assignment: string;
|
assignment: string;
|
||||||
corporateId: string;
|
corporateId: string;
|
||||||
|
level: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@@ -59,7 +64,42 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const assignmentUsers = [
|
const assignmentUsers = [
|
||||||
...new Set(assignments.flatMap((a) => a.assignees)),
|
...new Set(assignments.flatMap((a) => a.assignees)),
|
||||||
];
|
];
|
||||||
|
const assigners = [...new Set(assignments.map((a) => a.assigner))];
|
||||||
const users = await getSpecificUsers(assignmentUsers);
|
const users = await getSpecificUsers(assignmentUsers);
|
||||||
|
const assignerUsers = await getSpecificUsers(assigners);
|
||||||
|
|
||||||
|
const assignerUsersGradingSystems = await Promise.all(
|
||||||
|
assignerUsers.map(async (user: User) => {
|
||||||
|
const data = await getGradingSystem(user);
|
||||||
|
// in this context I need to override as I'll have to match to the assigner
|
||||||
|
return { ...data, user: user.id };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const getGradingSystemHelper = (
|
||||||
|
exams: {id: string; module: Module; assignee: string}[],
|
||||||
|
assigner: string,
|
||||||
|
user: User,
|
||||||
|
correct: number,
|
||||||
|
total: number
|
||||||
|
) => {
|
||||||
|
if (exams.some((e) => e.module === "level")) {
|
||||||
|
const gradingSystem = assignerUsersGradingSystems.find(
|
||||||
|
(gs) => gs.user === assigner
|
||||||
|
);
|
||||||
|
if (gradingSystem) {
|
||||||
|
const bandScore = calculateBandScore(
|
||||||
|
correct,
|
||||||
|
total,
|
||||||
|
"level",
|
||||||
|
user.focus
|
||||||
|
);
|
||||||
|
return getGradingLabel(bandScore, gradingSystem?.steps || []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "N/A";
|
||||||
|
};
|
||||||
|
|
||||||
const tableResults = assignments.reduce(
|
const tableResults = assignments.reduce(
|
||||||
(accmA: TableData[], a: AssignmentWithCorporateId) => {
|
(accmA: TableData[], a: AssignmentWithCorporateId) => {
|
||||||
@@ -67,14 +107,25 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const userStats =
|
const userStats =
|
||||||
a.results.find((r) => r.user === assignee)?.stats || [];
|
a.results.find((r) => r.user === assignee)?.stats || [];
|
||||||
const userData = users.find((u) => u.id === assignee);
|
const userData = users.find((u) => u.id === assignee);
|
||||||
const corporate = users.find((u) => u.id === a.assigner)?.name || "";
|
const corporateUser = users.find((u) => u.id === a.assigner);
|
||||||
|
const correct = userStats.reduce((n, e) => n + e.score.correct, 0);
|
||||||
|
const total = userStats.reduce((n, e) => n + e.score.total, 0);
|
||||||
|
const level = getGradingSystemHelper(
|
||||||
|
a.exams,
|
||||||
|
a.assigner,
|
||||||
|
userData!,
|
||||||
|
correct,
|
||||||
|
total
|
||||||
|
);
|
||||||
|
console.log("Level", level);
|
||||||
const commonData = {
|
const commonData = {
|
||||||
user: userData?.name || "",
|
user: userData?.name || "",
|
||||||
email: userData?.email || "",
|
email: userData?.email || "",
|
||||||
userId: assignee,
|
userId: assignee,
|
||||||
corporateId: a.corporateId,
|
corporateId: a.corporateId,
|
||||||
corporate,
|
corporate: corporateUser?.name || "",
|
||||||
assignment: a.name,
|
assignment: a.name,
|
||||||
|
level,
|
||||||
};
|
};
|
||||||
if (userStats.length === 0) {
|
if (userStats.length === 0) {
|
||||||
return {
|
return {
|
||||||
@@ -87,7 +138,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...commonData,
|
...commonData,
|
||||||
correct: userStats.reduce((n, e) => n + e.score.correct, 0),
|
correct,
|
||||||
submitted: true,
|
submitted: true,
|
||||||
date: moment.max(userStats.map((e) => moment(e.date))),
|
date: moment.max(userStats.map((e) => moment(e.date))),
|
||||||
};
|
};
|
||||||
@@ -129,11 +180,17 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Date",
|
label: "Date",
|
||||||
value: (entry: TableData) => entry.date?.format("YYYY/MM/DD") || '',
|
value: (entry: TableData) => entry.date?.format("YYYY/MM/DD") || "",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Level",
|
||||||
|
value: (entry: TableData) => entry.level,
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredSearch = searchText ? search(searchText, searchFilters, tableResults) : tableResults;
|
const filteredSearch = searchText
|
||||||
|
? search(searchText, searchFilters, tableResults)
|
||||||
|
: tableResults;
|
||||||
|
|
||||||
worksheet.addRow(headers.map((h) => h.label));
|
worksheet.addRow(headers.map((h) => h.label));
|
||||||
(filteredSearch as TableData[]).forEach((entry) => {
|
(filteredSearch as TableData[]).forEach((entry) => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {Grading} from "@/interfaces";
|
|||||||
import {getGroupsForUser} from "@/utils/groups.be";
|
import {getGroupsForUser} from "@/utils/groups.be";
|
||||||
import {uniq} from "lodash";
|
import {uniq} from "lodash";
|
||||||
import {getUser} from "@/utils/users.be";
|
import {getUser} from "@/utils/users.be";
|
||||||
|
import { getGradingSystem } from "@/utils/grading.be";
|
||||||
|
|
||||||
const db = getFirestore(app);
|
const db = getFirestore(app);
|
||||||
|
|
||||||
@@ -31,19 +32,8 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshot = await getDoc(doc(db, "grading", req.session.user.id));
|
const gradingSystem = await getGradingSystem(req.session.user);
|
||||||
if (snapshot.exists()) return res.status(200).json(snapshot.data());
|
return res.status(200).json(gradingSystem);
|
||||||
|
|
||||||
if (req.session.user.type !== "teacher" && req.session.user.type !== "student")
|
|
||||||
return res.status(200).json({steps: CEFR_STEPS, user: req.session.user.id});
|
|
||||||
|
|
||||||
const corporate = await getUserCorporate(req.session.user.id);
|
|
||||||
if (!corporate) return res.status(200).json(CEFR_STEPS);
|
|
||||||
|
|
||||||
const corporateSnapshot = await getDoc(doc(db, "grading", corporate.id));
|
|
||||||
if (corporateSnapshot.exists()) return res.status(200).json(snapshot.data());
|
|
||||||
|
|
||||||
return res.status(200).json({steps: CEFR_STEPS, user: req.session.user.id});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function post(req: NextApiRequest, res: NextApiResponse) {
|
async function post(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
|||||||
23
src/utils/grading.be.ts
Normal file
23
src/utils/grading.be.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { app } from "@/firebase";
|
||||||
|
import { getFirestore, doc, getDoc } from "firebase/firestore";
|
||||||
|
import { CEFR_STEPS } from "@/resources/grading";
|
||||||
|
import { getUserCorporate } from "@/utils/groups.be";
|
||||||
|
import { User } from "@/interfaces/user";
|
||||||
|
import { Grading } from "@/interfaces";
|
||||||
|
const db = getFirestore(app);
|
||||||
|
|
||||||
|
export const getGradingSystem = async (user: User): Promise<Grading> => {
|
||||||
|
const snapshot = await getDoc(doc(db, "grading", user.id));
|
||||||
|
if (snapshot.exists()) return snapshot.data() as Grading;
|
||||||
|
|
||||||
|
if (user.type !== "teacher" && user.type !== "student")
|
||||||
|
return { steps: CEFR_STEPS, user: user.id };
|
||||||
|
|
||||||
|
const corporate = await getUserCorporate(user.id);
|
||||||
|
if (!corporate) return { steps: CEFR_STEPS, user: user.id };
|
||||||
|
|
||||||
|
const corporateSnapshot = await getDoc(doc(db, "grading", corporate.id));
|
||||||
|
if (corporateSnapshot.exists()) return corporateSnapshot.data() as Grading;
|
||||||
|
|
||||||
|
return { steps: CEFR_STEPS, user: user.id };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user