Added final touches

This commit is contained in:
Joao Ramos
2024-08-21 00:37:33 +01:00
parent 65f8368708
commit eec1bb0c30
3 changed files with 234 additions and 119 deletions

View File

@@ -3,6 +3,7 @@ import React from "react";
import { Document, Page, View, Text, Image } from "@react-pdf/renderer"; import { Document, Page, View, Text, Image } from "@react-pdf/renderer";
import { ModuleScore } from "@/interfaces/module.scores"; import { ModuleScore } from "@/interfaces/module.scores";
import { styles } from "./styles"; import { styles } from "./styles";
import TestReportFooter from "./test.report.footer";
import { StyleSheet } from "@react-pdf/renderer"; import { StyleSheet } from "@react-pdf/renderer";
@@ -17,17 +18,16 @@ const customStyles = StyleSheet.create({
}, },
table: { table: {
width: "100%", width: "100%",
margin: "10px",
}, },
tableRow: { tableRow: {
flexDirection: "row", flexDirection: "row",
}, },
tableCol50: { tableCol70: {
width: "50%", // First column width (50%) width: "70%", // First column width (50%)
borderStyle: "solid", borderStyle: "solid",
borderWidth: 1, borderWidth: 1,
borderColor: "#000", borderColor: "#000",
padding: 5, // padding: 5,
}, },
tableCol25: { tableCol25: {
width: "16.67%", // Remaining four columns each get 1/6 of the total width (50% / 3 = 16.67%) width: "16.67%", // Remaining four columns each get 1/6 of the total width (50% / 3 = 16.67%)
@@ -43,11 +43,20 @@ const customStyles = StyleSheet.create({
borderColor: "#000", borderColor: "#000",
padding: 5, padding: 5,
}, },
tableCol10: {
width: "10%", // Width for each of the 5 sub-columns (50% / 5 = 20%)
borderStyle: "solid",
borderWidth: 1,
borderColor: "#000",
padding: 5,
},
tableCellHeader: { tableCellHeader: {
backgroundColor: "#d3d3d3", fontSize: 12,
fontWeight: "bold",
textAlign: "center", textAlign: "center",
}, },
tableCellHeaderColor: {
backgroundColor: "#d3d3d3",
},
tableCell: { tableCell: {
fontSize: 10, fontSize: 10,
textAlign: "center", textAlign: "center",
@@ -86,7 +95,7 @@ const LevelTestReport = ({
const defaultTextStyle = [styles.textFont, { fontSize: 8 }]; const defaultTextStyle = [styles.textFont, { fontSize: 8 }];
return ( return (
<Document> <Document>
<Page style={styles.body}> <Page style={styles.body} orientation="landscape">
<Text style={[styles.textFont, styles.textBold, { fontSize: 11 }]}> <Text style={[styles.textFont, styles.textBold, { fontSize: 11 }]}>
Corporate Name: {corporateName} Corporate Name: {corporateName}
</Text> </Text>
@@ -111,56 +120,97 @@ const LevelTestReport = ({
<View style={customStyles.table}> <View style={customStyles.table}>
{/* Header Row */} {/* Header Row */}
<View style={customStyles.tableRow}> <View style={customStyles.tableRow}>
<View style={customStyles.tableCol50}> <View
<Text style={customStyles.tableCellHeader}>Test sections</Text> style={[
customStyles.tableCol70,
customStyles.tableCellHeaderColor,
]}
>
<Text style={[customStyles.tableCellHeader, { padding: 5 }]}>
Test sections
</Text>
</View> </View>
<View style={customStyles.tableCol25}> <View
style={[
customStyles.tableCol20,
customStyles.tableCellHeaderColor,
]}
>
<Text style={customStyles.tableCellHeader}>Time spent</Text> <Text style={customStyles.tableCellHeader}>Time spent</Text>
</View> </View>
<View style={customStyles.tableCol25}> <View
style={[
customStyles.tableCol10,
customStyles.tableCellHeaderColor,
]}
>
<Text style={customStyles.tableCellHeader}>Score</Text> <Text style={customStyles.tableCellHeader}>Score</Text>
</View> </View>
</View> </View>
<View style={customStyles.tableRow}> <View style={customStyles.tableRow}>
<View style={customStyles.tableCol50}> <View style={customStyles.tableCol70}>
<View style={customStyles.tableRow}> <View style={customStyles.tableRow}>
{uniqueExercises.map((exercise, index) => ( {uniqueExercises.map((exercise, index) => (
<View style={customStyles.tableCol20} key={index}> <View
style={[
customStyles.tableCol20,
index !== uniqueExercises.length - 1
? { borderWidth: 0, borderRightWidth: 1 }
: { borderWidth: 0 },
]}
key={index}
>
<Text style={customStyles.tableCell}>Part {index + 1}</Text> <Text style={customStyles.tableCell}>Part {index + 1}</Text>
</View> </View>
))} ))}
</View> </View>
</View> </View>
<View style={customStyles.tableCol25}> <View style={customStyles.tableCol20}>
<Text style={customStyles.tableCell}></Text> <Text style={customStyles.tableCell}></Text>
</View> </View>
<View style={customStyles.tableCol25}> <View style={customStyles.tableCol10}>
<Text style={customStyles.tableCell}></Text> <Text style={customStyles.tableCell}></Text>
</View> </View>
</View> </View>
<View style={customStyles.tableRow}> <View style={customStyles.tableRow}>
<View style={customStyles.tableCol50}> <View style={customStyles.tableCol70}>
<View style={customStyles.tableRow}> <View style={customStyles.tableRow}>
{uniqueExercises.map((exercise, index) => ( {uniqueExercises.map((exercise, index) => (
<View style={customStyles.tableCol20} key={index}> <View
style={[
customStyles.tableCol20,
index !== uniqueExercises.length - 1
? { borderWidth: 0, borderRightWidth: 1 }
: { borderWidth: 0 },
]}
key={index}
>
<Text style={customStyles.tableCell}>{exercise.name}</Text> <Text style={customStyles.tableCell}>{exercise.name}</Text>
</View> </View>
))} ))}
</View> </View>
</View> </View>
<View style={customStyles.tableCol25}> <View style={customStyles.tableCol20}>
<Text style={customStyles.tableCell}></Text> <Text style={customStyles.tableCell}></Text>
</View> </View>
<View style={customStyles.tableCol25}> <View style={customStyles.tableCol10}>
<Text style={customStyles.tableCell}></Text> <Text style={customStyles.tableCell}></Text>
</View> </View>
</View> </View>
<View style={customStyles.tableRow}> <View style={customStyles.tableRow}>
<View style={customStyles.tableCol50}> <View style={customStyles.tableCol70}>
<View style={customStyles.tableRow}> <View style={customStyles.tableRow}>
{uniqueExercises.map((exercise, index) => ( {uniqueExercises.map((exercise, index) => (
<View style={customStyles.tableCol20} key={index}> <View
style={[
customStyles.tableCol20,
index !== uniqueExercises.length - 1
? { borderWidth: 0, borderRightWidth: 1 }
: { borderWidth: 0 },
]}
key={index}
>
<Text style={customStyles.tableCell}> <Text style={customStyles.tableCell}>
{exercise.result} {exercise.result}
</Text> </Text>
@@ -168,14 +218,15 @@ const LevelTestReport = ({
))} ))}
</View> </View>
</View> </View>
<View style={customStyles.tableCol25}> <View style={customStyles.tableCol20}>
<Text style={customStyles.tableCell}>{timeSpent}</Text> <Text style={customStyles.tableCell}>{timeSpent}</Text>
</View> </View>
<View style={customStyles.tableCol25}> <View style={customStyles.tableCol10}>
<Text style={customStyles.tableCell}>{score}</Text> <Text style={customStyles.tableCell}>{score}</Text>
</View> </View>
</View> </View>
</View> </View>
<TestReportFooter userId={userId} />
</Page> </Page>
</Document> </Document>
); );

View File

@@ -34,7 +34,7 @@ import {
streamToBuffer, streamToBuffer,
} from "@/utils/pdf"; } from "@/utils/pdf";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { getCorporateNameForStudent } from "@/utils/groups.be";
const db = getFirestore(app); const db = getFirestore(app);
export default withIronSessionApiRoute(handler, sessionOptions); export default withIronSessionApiRoute(handler, sessionOptions);
@@ -325,14 +325,14 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
} }
const userId = stats[statIndex].user; const userId = stats[statIndex].user;
// if (hasPDF) { if (hasPDF) {
// // if it does, return the pdf url // if it does, return the pdf url
// const fileRef = ref(storage, hasPDF.pdf!.path); const fileRef = ref(storage, hasPDF.pdf!.path);
// const url = await getDownloadURL(fileRef); const url = await getDownloadURL(fileRef);
// res.status(200).end(url); res.status(200).end(url);
// return; return;
// } }
try { try {
// generate the pdf report // generate the pdf report
@@ -355,7 +355,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
stats.reduce((accm, s: Stat) => accm + (s.timeSpent || 0), 0) / 60 stats.reduce((accm, s: Stat) => accm + (s.timeSpent || 0), 0) / 60
} minutes`; } minutes`;
const score = stats.reduce((accm, s) => accm + s.score.correct, 0); const score = stats.reduce((accm, s) => accm + s.score.correct, 0);
const corporateName = await getCorporateNameForStudent(userId);
const pdfStream = await ReactPDF.renderToStream( const pdfStream = await ReactPDF.renderToStream(
<LevelTestReport <LevelTestReport
date={moment.max(dates).format("DD/MM/YYYY")} date={moment.max(dates).format("DD/MM/YYYY")}
@@ -364,7 +364,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
id={stat.exam} id={stat.exam}
gender={user.demographicInformation?.gender || ""} gender={user.demographicInformation?.gender || ""}
passportId={user.demographicInformation?.passport_id || ""} passportId={user.demographicInformation?.passport_id || ""}
corporateName="TODO" corporateName={corporateName}
downloadDate={moment().format("DD/MM/YYYY")} downloadDate={moment().format("DD/MM/YYYY")}
userId={userId} userId={userId}
uniqueExercises={uniqueExercises} uniqueExercises={uniqueExercises}
@@ -376,8 +376,6 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
const url = await getPdfUrl(pdfStream, docsSnap); const url = await getPdfUrl(pdfStream, docsSnap);
res.status(200).end(url); res.status(200).end(url);
return; return;
return;
} }
const user = docUser.data() as User; const user = docUser.data() as User;

View File

@@ -1,12 +1,29 @@
import { app } from "@/firebase"; import { app } from "@/firebase";
import {CorporateUser, Group, StudentUser, TeacherUser} from "@/interfaces/user"; import {
import {collection, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore"; CorporateUser,
Group,
StudentUser,
TeacherUser,
} from "@/interfaces/user";
import {
collection,
doc,
getDoc,
getDocs,
getFirestore,
query,
setDoc,
where,
} from "firebase/firestore";
import moment from "moment"; import moment from "moment";
import { getUser } from "./users.be"; import { getUser } from "./users.be";
import { getSpecificUsers } from "./users.be";
const db = getFirestore(app); const db = getFirestore(app);
export const updateExpiryDateOnGroup = async (participantID: string, corporateID: string) => { export const updateExpiryDateOnGroup = async (
participantID: string,
corporateID: string
) => {
const corporateRef = await getDoc(doc(db, "users", corporateID)); const corporateRef = await getDoc(doc(db, "users", corporateID));
const participantRef = await getDoc(doc(db, "users", participantID)); const participantRef = await getDoc(doc(db, "users", participantID));
@@ -16,19 +33,36 @@ export const updateExpiryDateOnGroup = async (participantID: string, corporateID
...corporateRef.data(), ...corporateRef.data(),
id: corporateRef.id, id: corporateRef.id,
} as CorporateUser; } as CorporateUser;
const participant = {...participantRef.data(), id: participantRef.id} as StudentUser | TeacherUser; const participant = { ...participantRef.data(), id: participantRef.id } as
| StudentUser
| TeacherUser;
if (corporate.type !== "corporate" || (participant.type !== "student" && participant.type !== "teacher")) return; if (
corporate.type !== "corporate" ||
(participant.type !== "student" && participant.type !== "teacher")
)
return;
if (!corporate.subscriptionExpirationDate || !participant.subscriptionExpirationDate) { if (
return await setDoc(doc(db, "users", participant.id), {subscriptionExpirationDate: null}, {merge: true}); !corporate.subscriptionExpirationDate ||
!participant.subscriptionExpirationDate
) {
return await setDoc(
doc(db, "users", participant.id),
{ subscriptionExpirationDate: null },
{ merge: true }
);
} }
const corporateDate = moment(corporate.subscriptionExpirationDate); const corporateDate = moment(corporate.subscriptionExpirationDate);
const participantDate = moment(participant.subscriptionExpirationDate); const participantDate = moment(participant.subscriptionExpirationDate);
if (corporateDate.isAfter(participantDate)) if (corporateDate.isAfter(participantDate))
return await setDoc(doc(db, "users", participant.id), {subscriptionExpirationDate: corporateDate.toISOString()}, {merge: true}); return await setDoc(
doc(db, "users", participant.id),
{ subscriptionExpirationDate: corporateDate.toISOString() },
{ merge: true }
);
return; return;
}; };
@@ -39,17 +73,29 @@ export const getGroups = async () => {
}; };
export const getUserGroups = async (id: string): Promise<Group[]> => { export const getUserGroups = async (id: string): Promise<Group[]> => {
const groupDocs = await getDocs(query(collection(db, "groups"), where("admin", "==", id))); const groupDocs = await getDocs(
query(collection(db, "groups"), where("admin", "==", id))
);
return groupDocs.docs.map((x) => ({ ...x.data(), id })) as Group[]; return groupDocs.docs.map((x) => ({ ...x.data(), id })) as Group[];
}; };
export const getAllAssignersByCorporate = async (corporateID: string): Promise<string[]> => { export const getAllAssignersByCorporate = async (
corporateID: string
): Promise<string[]> => {
const groups = await getUserGroups(corporateID); const groups = await getUserGroups(corporateID);
const groupUsers = (await Promise.all(groups.map(async (g) => await Promise.all(g.participants.map(getUser))))).flat(); const groupUsers = (
await Promise.all(
groups.map(async (g) => await Promise.all(g.participants.map(getUser)))
)
).flat();
const teacherPromises = await Promise.all( const teacherPromises = await Promise.all(
groupUsers.map(async (u) => groupUsers.map(async (u) =>
u.type === "teacher" ? u.id : u.type === "corporate" ? [...(await getAllAssignersByCorporate(u.id)), u.id] : undefined, u.type === "teacher"
), ? u.id
: u.type === "corporate"
? [...(await getAllAssignersByCorporate(u.id)), u.id]
: undefined
)
); );
return teacherPromises.filter((x) => !!x).flat() as string[]; return teacherPromises.filter((x) => !!x).flat() as string[];
@@ -80,7 +126,10 @@ export const getGroupsForUser = async (admin: string, participant: string) => {
} }
}; };
export const getStudentGroupsForUsersWithoutAdmin = async (admin: string, participants: string[]) => { export const getStudentGroupsForUsersWithoutAdmin = async (
admin: string,
participants: string[]
) => {
try { try {
const queryConstraints = [ const queryConstraints = [
...(admin ? [where("admin", "!=", admin)] : []), ...(admin ? [where("admin", "!=", admin)] : []),
@@ -105,3 +154,20 @@ export const getGroupsForUser = async (admin: string, participant: string) => {
return []; return [];
} }
}; };
export const getCorporateNameForStudent = async (studentID: string) => {
const groups = await getStudentGroupsForUsersWithoutAdmin("", [studentID]);
if (groups.length === 0) return '';
const adminUserIds = [...new Set(groups.map((g) => g.admin))];
const adminUsersData = await getSpecificUsers(adminUserIds);
if(adminUsersData.length === 0) return '';
const admins = adminUsersData.filter((x) => x.type === 'corporate');
if(admins.length > 0) {
return (admins[0] as CorporateUser).corporateInformation.companyInformation.name;
}
return '';
};