Added some code comments

This commit is contained in:
Joao Ramos
2024-01-08 18:58:54 +00:00
parent e6c82412bf
commit 12d608879d
2 changed files with 162 additions and 119 deletions

View File

@@ -124,7 +124,10 @@ const PDFReport = ({
{testDetails {testDetails
.filter(({ feedback }) => feedback) .filter(({ feedback }) => feedback)
.map(({ module, feedback }) => ( .map(({ module, feedback }) => (
<View key={module}> <View key={module} style={{
display: 'flex',
gap: 4,
}}>
<Text style={[...defaultSkillsTitleStyle, styles.textBold]}> <Text style={[...defaultSkillsTitleStyle, styles.textBold]}>
{module} {module}
</Text> </Text>
@@ -142,7 +145,8 @@ const PDFReport = ({
/> />
</View> </View>
</View> </View>
<View style={[{ paddingTop: 30 }, styles.separator]}> <View style={[{ paddingBottom: 30 }, styles.separator]}></View>
{false && (
<View> <View>
<ProgressBar <ProgressBar
width={200} width={200}
@@ -152,7 +156,7 @@ const PDFReport = ({
percentage={60} percentage={60}
/> />
</View> </View>
</View> )}
<View style={{ flexGrow: 1 }}></View> <View style={{ flexGrow: 1 }}></View>
<View style={[{ paddingTop: 30, fontSize: 5 }, styles.textFont]}> <View style={[{ paddingTop: 30, fontSize: 5 }, styles.textFont]}>
<View <View

View File

@@ -94,31 +94,6 @@ const getPerformanceSummary = (module: Module, score: number) => {
if (module === "level") return getLevelSummary(score); if (module === "level") return getLevelSummary(score);
return getExamSummary(score); return getExamSummary(score);
}; };
const getListeningFeedback = () =>
"Your listening skills are exceptional. You display a high level of attentiveness, accurately understanding spoken information across various contexts. Your ability to follow instructions and discern details from spoken content reflects a strong foundation in auditory comprehension. To further refine this skill, continue exposing yourself to diverse listening materials, including podcasts, interviews, and authentic conversations.";
const getReadingFeedback = () =>
"Your reading skills are advanced, demonstrating a keen ability to comprehend and analyse written texts. You not only grasp the main ideas effectively but also excel in identifying supporting details and drawing inferences from context. Your enthusiasm for reading is evident, and I encourage you to explore more diverse and challenging materials to further expand your vocabulary and enhance your critical thinking skills.";
const getWritingFeedback = () =>
"In the realm of writing, you showcase a commendable command of language. Your ability to construct well-organized and coherent sentences is notable. You exhibit a strong grasp of grammar and punctuation, contributing to the overall clarity of your written expression. Continue refining your writing style, and consider experimenting with different genres to unleash your creative potential.";
const getSpeakingFeedback = () =>
"Your oral communication skills are a standout feature of your language proficiency. You articulate ideas with clarity and confidence, actively participating in discussions. Your ability to express yourself verbally is a valuable asset. To enhance your speaking skills even further, consider taking on leadership roles in group activities and engaging in more challenging speaking tasks, such as presentations and debates.";
const getFeedback = (module: Module) => {
switch (module) {
case "listening":
return getListeningFeedback();
case "reading":
return getReadingFeedback();
case "writing":
return getWritingFeedback();
case "speaking":
return getSpeakingFeedback();
default:
return "";
}
};
interface SkillsFeedbackRequest { interface SkillsFeedbackRequest {
code: Module; code: Module;
name: string; name: string;
@@ -144,7 +119,23 @@ const getSkillsFeedback = async (sections: SkillsFeedbackRequest[]) => {
return backendRequest.data?.sections; return backendRequest.data?.sections;
} catch (err) { } catch (err) {
console.log(err); return err;
}
};
const handleSkillsFeedbackRequest = async (
sections: SkillsFeedbackRequest[]
): Promise<SkillsFeedbackResponse[] | null> => {
let i = 0;
try {
const data = await getSkillsFeedback(sections);
return data;
} catch (err) {
if (i < 3) {
i++;
return handleSkillsFeedbackRequest(sections);
}
return null; return null;
} }
}; };
@@ -173,15 +164,10 @@ const getRadialProgressPNG = (
}; };
async function post(req: NextApiRequest, res: NextApiResponse) { async function post(req: NextApiRequest, res: NextApiResponse) {
// verify if it's a logged user that is trying to export
if (req.session.user) { if (req.session.user) {
const { id } = req.query as { id: string }; const { id } = req.query as { id: string };
// const codeCheckerRef = await getDocs( // fetch stats entries for this particular user with the requested exam session
// query(collection(db, "codes"), where("checkout", "==", checkout))
// );
// const docRef = doc(db, "stats", id).where;
// const docSnap = await getDoc(docRef);
const docsSnap = await getDocs( const docsSnap = await getDocs(
query( query(
collection(db, "stats"), collection(db, "stats"),
@@ -195,15 +181,39 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
return; return;
} }
const stats = docsSnap.docs.map((d) => d.data());
// verify if the stats already have a pdf generated
const hasPDF = stats.find((s) => s.pdf);
if (hasPDF) {
// if it does, return the pdf url
res.status(200).end(hasPDF.pdf);
return;
}
try {
// generate the pdf report
const docUser = await getDoc(doc(db, "users", req.session.user.id)); const docUser = await getDoc(doc(db, "users", req.session.user.id));
if (docUser.exists()) { if (docUser.exists()) {
// we'll need the user in order to get the user data (name, email, focus, etc);
const user = docUser.data() as User; const user = docUser.data() as User;
const stats = docsSnap.docs.map((d) => d.data()); // generate the QR code for the report
const qrcode = await generateQRCode(
(req.headers.origin || "") + req.url
);
if (!qrcode) {
res.status(500).json({ ok: false });
return;
}
// stats may contain multiple exams of the same type so we need to aggregate them
const results = ( const results = (
stats.reduce((accm: ModuleScore[], { module, score }) => { stats.reduce((accm: ModuleScore[], { module, score }) => {
const fixedModuleStr = module[0].toUpperCase() + module.substring(1); const fixedModuleStr =
module[0].toUpperCase() + module.substring(1);
if (accm.find((e: ModuleScore) => e.module === fixedModuleStr)) { if (accm.find((e: ModuleScore) => e.module === fixedModuleStr)) {
return accm.map((e: ModuleScore) => { return accm.map((e: ModuleScore) => {
if (e.module === fixedModuleStr) { if (e.module === fixedModuleStr) {
@@ -224,13 +234,13 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
module: fixedModuleStr, module: fixedModuleStr,
score: score.correct, score: score.correct,
total: score.total, total: score.total,
feedback: getFeedback(module),
code: module, code: module,
}, },
]; ];
}, []) as ModuleScore[] }, []) as ModuleScore[]
).map((moduleScore) => { ).map((moduleScore) => {
const { score, total } = moduleScore; const { score, total } = moduleScore;
// with all the scores aggreated we can calculate the band score for each module
const bandScore = calculateBandScore( const bandScore = calculateBandScore(
score, score,
total, total,
@@ -240,23 +250,30 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
return { return {
...moduleScore, ...moduleScore,
// generate the closest radial progress png for the score
png: getRadialProgressPNG("azul", score, total), png: getRadialProgressPNG("azul", score, total),
bandScore, bandScore,
}; };
}); });
const skillsFeedback = // get the skills feedback from the backend based on the module grade
(await getSkillsFeedback( const skillsFeedback = (await handleSkillsFeedbackRequest(
results.map(({ code, bandScore }) => ({ results.map(({ code, bandScore }) => ({
code, code,
name: moduleLabels[code], name: moduleLabels[code],
grade: bandScore, grade: bandScore,
})) }))
)) || ([] as SkillsFeedbackResponse[]); )) as SkillsFeedbackResponse[];
if (!skillsFeedback) {
res.status(500).json({ ok: false });
return;
}
// assign the feedback to the results
const finalResults = results.map((result) => { const finalResults = results.map((result) => {
const feedback = skillsFeedback.find( const feedback = skillsFeedback.find(
(f: SkillsFeedbackResponse) => f.code === result.module (f: SkillsFeedbackResponse) => f.code === result.code
); );
if (feedback) { if (feedback) {
@@ -269,17 +286,28 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
return result; return result;
}); });
const [stat] = stats as Stat[]; // generate the file ref for storage
const fileName = `${Date.now().toString()}.pdf`; const fileName = `${Date.now().toString()}.pdf`;
const fileRef = ref(storage, `exam_report/${fileName}`); const fileRef = ref(storage, `exam_report/${fileName}`);
const overallScore = results.reduce((accm, { score }) => accm + score, 0);
const overallTotal = results.reduce((accm, { total }) => accm + total, 0); // calculate the overall score out of all the aggregated results
const overallScore = results.reduce(
(accm, { score }) => accm + score,
0
);
const overallTotal = results.reduce(
(accm, { total }) => accm + total,
0
);
const overallResult = overallScore / overallTotal; const overallResult = overallScore / overallTotal;
const performanceSummary = getPerformanceSummary("level", overallResult);
const qrcode = await generateQRCode((req.headers.origin || "") + req.url); // generate the performance summary based on the overall result
const performanceSummary = getPerformanceSummary(
"level",
overallResult
);
// generate the overall detail report
const overallDetail = { const overallDetail = {
module: "Overall", module: "Overall",
score: overallScore, score: overallScore,
@@ -287,14 +315,16 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
png: getRadialProgressPNG("laranja", overallScore, overallTotal), png: getRadialProgressPNG("laranja", overallScore, overallTotal),
} as ModuleScore; } as ModuleScore;
const testDetails = [overallDetail, ...finalResults]; const testDetails = [overallDetail, ...finalResults];
const [stat] = stats;
// level exams have a different report structure than the skill exams
const renderDetails = () => { const renderDetails = () => {
if (stats[0].module === "level") { if (stat.module === "level") {
return <LevelExamDetails detail={overallDetail} />; return <LevelExamDetails detail={overallDetail} />;
} }
return <SkillExamDetails testDetails={testDetails} />; return <SkillExamDetails testDetails={testDetails} />;
}; };
if (qrcode) {
const pdfStream = await ReactPDF.renderToStream( const pdfStream = await ReactPDF.renderToStream(
<PDFReport <PDFReport
date={new Date(stat.date).toLocaleString()} date={new Date(stat.date).toLocaleString()}
@@ -310,10 +340,13 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
/> />
); );
// upload the pdf to storage
const pdfBuffer = await streamToBuffer(pdfStream); const pdfBuffer = await streamToBuffer(pdfStream);
const snapshot = await uploadBytes(fileRef, pdfBuffer, { const snapshot = await uploadBytes(fileRef, pdfBuffer, {
contentType: "application/pdf", contentType: "application/pdf",
}); });
// update the stats entries with the pdf url to prevent duplication
docsSnap.docs.forEach(async (doc) => { docsSnap.docs.forEach(async (doc) => {
await updateDoc(doc.ref, { await updateDoc(doc.ref, {
pdf: snapshot.ref.fullPath, pdf: snapshot.ref.fullPath,
@@ -322,6 +355,12 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
res.status(200).end(snapshot.ref.fullPath); res.status(200).end(snapshot.ref.fullPath);
return; return;
} }
res.status(401).json({ ok: false });
return;
} catch (err) {
res.status(500).json({ ok: false });
return;
} }
} }