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,106 +181,150 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
return; return;
} }
const docUser = await getDoc(doc(db, "users", req.session.user.id)); 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 (docUser.exists()) { if (hasPDF) {
const user = docUser.data() as User; // if it does, return the pdf url
res.status(200).end(hasPDF.pdf);
return;
}
const stats = docsSnap.docs.map((d) => d.data()); try {
const results = ( // generate the pdf report
stats.reduce((accm: ModuleScore[], { module, score }) => { const docUser = await getDoc(doc(db, "users", req.session.user.id));
const fixedModuleStr = module[0].toUpperCase() + module.substring(1);
if (accm.find((e: ModuleScore) => e.module === fixedModuleStr)) {
return accm.map((e: ModuleScore) => {
if (e.module === fixedModuleStr) {
return {
...e,
score: e.score + score.correct,
total: e.total + score.total,
};
}
return e; 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;
return [ // generate the QR code for the report
...accm, const qrcode = await generateQRCode(
{ (req.headers.origin || "") + req.url
module: fixedModuleStr,
score: score.correct,
total: score.total,
feedback: getFeedback(module),
code: module,
},
];
}, []) as ModuleScore[]
).map((moduleScore) => {
const { score, total } = moduleScore;
const bandScore = calculateBandScore(
score,
total,
moduleScore.code as Module,
user.focus
); );
return { if (!qrcode) {
...moduleScore, res.status(500).json({ ok: false });
png: getRadialProgressPNG("azul", score, total), return;
bandScore, }
};
});
const skillsFeedback = // stats may contain multiple exams of the same type so we need to aggregate them
(await getSkillsFeedback( const results = (
stats.reduce((accm: ModuleScore[], { module, score }) => {
const fixedModuleStr =
module[0].toUpperCase() + module.substring(1);
if (accm.find((e: ModuleScore) => e.module === fixedModuleStr)) {
return accm.map((e: ModuleScore) => {
if (e.module === fixedModuleStr) {
return {
...e,
score: e.score + score.correct,
total: e.total + score.total,
};
}
return e;
});
}
return [
...accm,
{
module: fixedModuleStr,
score: score.correct,
total: score.total,
code: module,
},
];
}, []) as ModuleScore[]
).map((moduleScore) => {
const { score, total } = moduleScore;
// with all the scores aggreated we can calculate the band score for each module
const bandScore = calculateBandScore(
score,
total,
moduleScore.code as Module,
user.focus
);
return {
...moduleScore,
// generate the closest radial progress png for the score
png: getRadialProgressPNG("azul", score, total),
bandScore,
};
});
// get the skills feedback from the backend based on the module grade
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[];
const finalResults = results.map((result) => { if (!skillsFeedback) {
const feedback = skillsFeedback.find( res.status(500).json({ ok: false });
(f: SkillsFeedbackResponse) => f.code === result.module return;
}
// assign the feedback to the results
const finalResults = results.map((result) => {
const feedback = skillsFeedback.find(
(f: SkillsFeedbackResponse) => f.code === result.code
);
if (feedback) {
return {
...result,
feedback: feedback?.evaluation + " " + feedback?.suggestions,
};
}
return result;
});
// generate the file ref for storage
const fileName = `${Date.now().toString()}.pdf`;
const fileRef = ref(storage, `exam_report/${fileName}`);
// 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;
// generate the performance summary based on the overall result
const performanceSummary = getPerformanceSummary(
"level",
overallResult
); );
if (feedback) { // generate the overall detail report
return { const overallDetail = {
...result, module: "Overall",
feedback: feedback?.evaluation + " " + feedback?.suggestions, score: overallScore,
}; total: overallTotal,
} png: getRadialProgressPNG("laranja", overallScore, overallTotal),
} as ModuleScore;
const testDetails = [overallDetail, ...finalResults];
return result; const [stat] = stats;
}); // level exams have a different report structure than the skill exams
const renderDetails = () => {
if (stat.module === "level") {
return <LevelExamDetails detail={overallDetail} />;
}
const [stat] = stats as Stat[]; return <SkillExamDetails testDetails={testDetails} />;
};
const fileName = `${Date.now().toString()}.pdf`;
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);
const overallResult = overallScore / overallTotal;
const performanceSummary = getPerformanceSummary("level", overallResult);
const qrcode = await generateQRCode((req.headers.origin || "") + req.url);
const overallDetail = {
module: "Overall",
score: overallScore,
total: overallTotal,
png: getRadialProgressPNG("laranja", overallScore, overallTotal),
} as ModuleScore;
const testDetails = [overallDetail, ...finalResults];
const renderDetails = () => {
if (stats[0].module === "level") {
return <LevelExamDetails detail={overallDetail} />;
}
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;
} }
} }