feat: monitoring data to csv
This commit is contained in:
parent
cbd3977858
commit
c5aecb2af9
|
|
@ -1,6 +1,6 @@
|
||||||
APP_PORT = 3001
|
APP_PORT = 3001
|
||||||
NODE_ENV = development
|
NODE_ENV = development
|
||||||
FE_DOMAIN = http://localhost:5173
|
CLIENT_URL = http://localhost:5173
|
||||||
|
|
||||||
DB_HOST = localhost
|
DB_HOST = localhost
|
||||||
DB_USER = root
|
DB_USER = root
|
||||||
|
|
|
||||||
|
|
@ -389,7 +389,7 @@ export const forgotPassword = async (req, res) => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetLink = `${process.env.FE_DOMAIN}/resetPassword/${resetToken}`;
|
const resetLink = `${process.env.CLIENT_URL}/resetPassword/${resetToken}`;
|
||||||
|
|
||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: process.env.EMAIL_USER,
|
from: process.env.EMAIL_USER,
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,6 @@ export const getStudentAnswersByStdLearningId = async (req, res) => {
|
||||||
const exerciseDetails = exercise.stdExerciseExercises;
|
const exerciseDetails = exercise.stdExerciseExercises;
|
||||||
const questionType = exerciseDetails.QUESTION_TYPE;
|
const questionType = exerciseDetails.QUESTION_TYPE;
|
||||||
|
|
||||||
// Create the formatted exercise object
|
|
||||||
const formattedExercise = {
|
const formattedExercise = {
|
||||||
ID_STUDENT_EXERCISE: exerciseData.ID_STUDENT_EXERCISE,
|
ID_STUDENT_EXERCISE: exerciseData.ID_STUDENT_EXERCISE,
|
||||||
ID_ADMIN_EXERCISE: exerciseDetails.ID_ADMIN_EXERCISE,
|
ID_ADMIN_EXERCISE: exerciseDetails.ID_ADMIN_EXERCISE,
|
||||||
|
|
@ -183,7 +182,6 @@ export const getStudentAnswersByStdLearningId = async (req, res) => {
|
||||||
RESULT_SCORE_STUDENT: exercise.RESULT_SCORE_STUDENT,
|
RESULT_SCORE_STUDENT: exercise.RESULT_SCORE_STUDENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Include appropriate details based on question type
|
|
||||||
if (questionType === "MCQ") {
|
if (questionType === "MCQ") {
|
||||||
formattedExercise.multipleChoices = exerciseDetails.multipleChoices;
|
formattedExercise.multipleChoices = exerciseDetails.multipleChoices;
|
||||||
} else if (questionType === "MPQ") {
|
} else if (questionType === "MPQ") {
|
||||||
|
|
@ -193,12 +191,22 @@ export const getStudentAnswersByStdLearningId = async (req, res) => {
|
||||||
return formattedExercise;
|
return formattedExercise;
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(200, {
|
return response(
|
||||||
...stdLearningData,
|
200,
|
||||||
stdExercises: mappedExercises,
|
{
|
||||||
}, "Student learning exercises retrieved successfully", res);
|
...stdLearningData,
|
||||||
|
stdExercises: mappedExercises,
|
||||||
|
},
|
||||||
|
"Student learning exercises retrieved successfully",
|
||||||
|
res
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return response(500, null, "Error retrieving student learning exercises", res);
|
return response(
|
||||||
|
500,
|
||||||
|
null,
|
||||||
|
"Error retrieving student learning exercises",
|
||||||
|
res
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import response from "../../response.js";
|
import response from "../../response.js";
|
||||||
import models from "../../models/index.js";
|
import models from "../../models/index.js";
|
||||||
|
import { createObjectCsvWriter } from "csv-writer";
|
||||||
|
import { Readable } from "stream";
|
||||||
|
import fs from "fs";
|
||||||
|
import os from "os";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
export const getMonitorings = async (req, res) => {
|
export const getMonitorings = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -373,7 +378,21 @@ export const monitoringStudentProgressById = async (req, res) => {
|
||||||
} else if (sort === "score") {
|
} else if (sort === "score") {
|
||||||
return b.SCORE - a.SCORE;
|
return b.SCORE - a.SCORE;
|
||||||
} else if (sort === "feedback") {
|
} else if (sort === "feedback") {
|
||||||
return a.FEEDBACK_STUDENT.localeCompare(b.FEEDBACK_STUDENT);
|
if (a.FEEDBACK_STUDENT === null && b.FEEDBACK_STUDENT !== null) {
|
||||||
|
return 1;
|
||||||
|
} else if (
|
||||||
|
a.FEEDBACK_STUDENT !== null &&
|
||||||
|
b.FEEDBACK_STUDENT === null
|
||||||
|
) {
|
||||||
|
return -1;
|
||||||
|
} else if (
|
||||||
|
a.FEEDBACK_STUDENT === null &&
|
||||||
|
b.FEEDBACK_STUDENT === null
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return a.FEEDBACK_STUDENT.localeCompare(b.FEEDBACK_STUDENT);
|
||||||
|
}
|
||||||
} else if (sort === "start") {
|
} else if (sort === "start") {
|
||||||
return new Date(a.STUDENT_START) - new Date(b.STUDENT_START);
|
return new Date(a.STUDENT_START) - new Date(b.STUDENT_START);
|
||||||
} else if (sort === "finish") {
|
} else if (sort === "finish") {
|
||||||
|
|
@ -848,27 +867,41 @@ export const getClassMonitoringDataByClassAndTopic = async (req, res) => {
|
||||||
"STUDENT_START",
|
"STUDENT_START",
|
||||||
"STUDENT_FINISH",
|
"STUDENT_FINISH",
|
||||||
],
|
],
|
||||||
|
order: [["STUDENT_FINISH", "DESC"]],
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedRecords = allStdLearning.sort((a, b) => {
|
const sortedRecords = allStdLearning.sort((a, b) => {
|
||||||
|
const userComparison = a.learningUser.NAME_USERS.localeCompare(
|
||||||
|
b.learningUser.NAME_USERS
|
||||||
|
);
|
||||||
|
if (userComparison !== 0) {
|
||||||
|
return userComparison;
|
||||||
|
}
|
||||||
|
|
||||||
if (sort === "nisn") {
|
if (sort === "nisn") {
|
||||||
return String(a.learningUser.students.NISN).localeCompare(
|
return String(a.learningUser.students.NISN).localeCompare(
|
||||||
String(b.learningUser.students.NISN)
|
String(b.learningUser.students.NISN)
|
||||||
);
|
);
|
||||||
} else if (sort === "user") {
|
} else if (sort === "name") {
|
||||||
return a.learningUser.NAME_USERS.localeCompare(
|
return userComparison;
|
||||||
b.learningUser.NAME_USERS
|
|
||||||
);
|
|
||||||
} else if (sort === "level") {
|
} else if (sort === "level") {
|
||||||
return a.level.NAME_LEVEL.localeCompare(b.level.NAME_LEVEL);
|
return a.level.NAME_LEVEL.localeCompare(b.level.NAME_LEVEL);
|
||||||
} else if (sort === "score") {
|
} else if (sort === "score") {
|
||||||
return b.SCORE - a.SCORE;
|
return b.SCORE - a.SCORE;
|
||||||
} else if (sort === "feedback") {
|
} else if (sort === "feedback") {
|
||||||
return a.FEEDBACK_STUDENT.localeCompare(b.FEEDBACK_STUDENT);
|
if (a.FEEDBACK_STUDENT === null && b.FEEDBACK_STUDENT !== null) {
|
||||||
|
return 1;
|
||||||
|
} else if (a.FEEDBACK_STUDENT !== null && b.FEEDBACK_STUDENT === null) {
|
||||||
|
return -1;
|
||||||
|
} else if (a.FEEDBACK_STUDENT === null && b.FEEDBACK_STUDENT === null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return a.FEEDBACK_STUDENT.localeCompare(b.FEEDBACK_STUDENT);
|
||||||
|
}
|
||||||
} else if (sort === "start") {
|
} else if (sort === "start") {
|
||||||
return new Date(a.STUDENT_START) - new Date(b.STUDENT_START);
|
return new Date(a.STUDENT_START) - new Date(b.STUDENT_START);
|
||||||
}
|
}
|
||||||
// Default sorting by student finish
|
|
||||||
return new Date(a.STUDENT_FINISH) - new Date(b.STUDENT_FINISH);
|
return new Date(a.STUDENT_FINISH) - new Date(b.STUDENT_FINISH);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -956,8 +989,6 @@ export const monitoringFeedbackByClassAndTopic = async (req, res) => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Monitoring Data:", JSON.stringify(monitoringData, null, 2));
|
|
||||||
|
|
||||||
if (!monitoringData || monitoringData.length === 0) {
|
if (!monitoringData || monitoringData.length === 0) {
|
||||||
return response(
|
return response(
|
||||||
404,
|
404,
|
||||||
|
|
@ -1027,3 +1058,375 @@ export const monitoringFeedbackByClassAndTopic = async (req, res) => {
|
||||||
response(500, null, "Error updating teacher feedback!", res);
|
response(500, null, "Error updating teacher feedback!", res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const monitoringStudentProgressCSVById = async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const monitoring = await models.Monitoring.findOne({
|
||||||
|
where: { ID_MONITORING: id },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.StdLearning,
|
||||||
|
as: "stdLearningMonitoring",
|
||||||
|
attributes: [
|
||||||
|
"ID",
|
||||||
|
"ID_LEVEL",
|
||||||
|
"SCORE",
|
||||||
|
"FEEDBACK_STUDENT",
|
||||||
|
"STUDENT_START",
|
||||||
|
"STUDENT_FINISH",
|
||||||
|
"ID_STUDENT_LEARNING",
|
||||||
|
],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Level,
|
||||||
|
as: "level",
|
||||||
|
attributes: ["ID_TOPIC", "NAME_LEVEL"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Topic,
|
||||||
|
as: "levelTopic",
|
||||||
|
attributes: ["NAME_TOPIC"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Section,
|
||||||
|
as: "topicSection",
|
||||||
|
attributes: ["NAME_SECTION"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: models.User,
|
||||||
|
as: "learningUser",
|
||||||
|
attributes: ["NAME_USERS"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Student,
|
||||||
|
as: "students",
|
||||||
|
attributes: ["NISN"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!monitoring) {
|
||||||
|
return response(404, null, "Monitoring data not found!", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stdLearning = monitoring.stdLearningMonitoring;
|
||||||
|
|
||||||
|
if (!stdLearning || stdLearning.length === 0) {
|
||||||
|
return response(404, null, "No student learning data found!", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userID = stdLearning.ID;
|
||||||
|
const topicID = stdLearning.level.ID_TOPIC;
|
||||||
|
const studentName = stdLearning.learningUser.NAME_USERS;
|
||||||
|
const nisn = stdLearning.learningUser.students.NISN;
|
||||||
|
const topicName = stdLearning.level.levelTopic.NAME_TOPIC;
|
||||||
|
const sectionName = stdLearning.level.levelTopic.topicSection.NAME_SECTION;
|
||||||
|
|
||||||
|
const levels = await models.StdLearning.findAll({
|
||||||
|
where: {
|
||||||
|
ID: userID,
|
||||||
|
STUDENT_FINISH: {
|
||||||
|
[models.Sequelize.Op.ne]: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Level,
|
||||||
|
as: "level",
|
||||||
|
attributes: ["NAME_LEVEL", "ID_TOPIC"],
|
||||||
|
where: {
|
||||||
|
ID_TOPIC: topicID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attributes: [
|
||||||
|
"SCORE",
|
||||||
|
"FEEDBACK_STUDENT",
|
||||||
|
"STUDENT_START",
|
||||||
|
"STUDENT_FINISH",
|
||||||
|
],
|
||||||
|
order: [["STUDENT_FINISH", "DESC"]],
|
||||||
|
distinct: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelArray = levels.map((learning) => ({
|
||||||
|
NAME_LEVEL: learning.level.NAME_LEVEL,
|
||||||
|
SCORE: learning.SCORE,
|
||||||
|
FEEDBACK_STUDENT: learning.FEEDBACK_STUDENT,
|
||||||
|
STUDENT_START: learning.STUDENT_START,
|
||||||
|
STUDENT_FINISH: learning.STUDENT_FINISH,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const tempDir = os.tmpdir();
|
||||||
|
const tempFilePath = path.join(
|
||||||
|
tempDir,
|
||||||
|
`Student_Monitoring_${nisn}_${studentName}.csv`
|
||||||
|
);
|
||||||
|
|
||||||
|
const csvWriter = createObjectCsvWriter({
|
||||||
|
path: tempFilePath,
|
||||||
|
header: [
|
||||||
|
{ id: "field", title: "Field" },
|
||||||
|
{ id: "value", title: "Value" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const records = [
|
||||||
|
{ field: "Section Name", value: sectionName },
|
||||||
|
{ field: "Topic Name", value: topicName },
|
||||||
|
{ field: "Student Name", value: studentName },
|
||||||
|
{ field: "NISN", value: nisn },
|
||||||
|
];
|
||||||
|
|
||||||
|
await csvWriter.writeRecords(records);
|
||||||
|
|
||||||
|
const levelCsvWriter = createObjectCsvWriter({
|
||||||
|
path: tempFilePath,
|
||||||
|
append: true,
|
||||||
|
header: [
|
||||||
|
{ id: "NAME_LEVEL", title: "Level Name" },
|
||||||
|
{ id: "SCORE", title: "Score" },
|
||||||
|
{ id: "FEEDBACK_STUDENT", title: "Student Feedback" },
|
||||||
|
{ id: "STUDENT_START", title: "Start Exercise" },
|
||||||
|
{ id: "STUDENT_FINISH", title: "Finish Exercise" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.appendFileSync(tempFilePath, "\n");
|
||||||
|
|
||||||
|
fs.appendFileSync(
|
||||||
|
tempFilePath,
|
||||||
|
"Name Level,Score,Student Feedback,Start Exercise,Finish Exercise\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
await levelCsvWriter.writeRecords(levelArray);
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "text/csv");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="Student_Monitoring_${nisn}_${studentName}.csv.csv"`
|
||||||
|
);
|
||||||
|
|
||||||
|
const fileStream = fs.createReadStream(tempFilePath);
|
||||||
|
fileStream.pipe(res).on("finish", () => {
|
||||||
|
fs.unlinkSync(tempFilePath);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
response(500, null, "Error retrieving student progress!", res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const classMonitoringCSVByClassAndTopic = async (req, res) => {
|
||||||
|
const { ID_CLASS, ID_TOPIC } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const classData = await models.Class.findOne({
|
||||||
|
where: { ID_CLASS: ID_CLASS },
|
||||||
|
attributes: ["ID_CLASS", "NAME_CLASS"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!classData) {
|
||||||
|
return response(404, null, "Class not found!", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = classData.NAME_CLASS;
|
||||||
|
|
||||||
|
const topicData = await models.Topic.findOne({
|
||||||
|
where: { ID_TOPIC: ID_TOPIC },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Section,
|
||||||
|
as: "topicSection",
|
||||||
|
attributes: ["ID_SECTION", "NAME_SECTION"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attributes: ["ID_TOPIC", "NAME_TOPIC"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!topicData) {
|
||||||
|
return response(404, null, "Topic not found!", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const topicName = topicData.NAME_TOPIC;
|
||||||
|
const sectionName = topicData.topicSection?.NAME_SECTION;
|
||||||
|
|
||||||
|
const monitoringData = await models.Monitoring.findAll({
|
||||||
|
where: { ID_CLASS: ID_CLASS },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.StdLearning,
|
||||||
|
as: "stdLearningMonitoring",
|
||||||
|
required: true,
|
||||||
|
attributes: ["ID", "ID_LEVEL"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Level,
|
||||||
|
as: "level",
|
||||||
|
attributes: ["ID_LEVEL", "ID_TOPIC"],
|
||||||
|
where: { ID_TOPIC: ID_TOPIC },
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!monitoringData || monitoringData.length === 0) {
|
||||||
|
const result = {
|
||||||
|
ID_CLASS: classData.ID_CLASS,
|
||||||
|
ID_SECTION: topicData.topicSection?.ID_SECTION,
|
||||||
|
ID_TOPIC: topicData.ID_TOPIC,
|
||||||
|
NAME_CLASS: className,
|
||||||
|
NAME_SECTION: sectionName,
|
||||||
|
NAME_TOPIC: topicName,
|
||||||
|
};
|
||||||
|
return response(
|
||||||
|
200,
|
||||||
|
result,
|
||||||
|
"No monitoring data found for this class and topic!",
|
||||||
|
res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userIds = monitoringData.map(
|
||||||
|
(monitoring) => monitoring.stdLearningMonitoring.ID
|
||||||
|
);
|
||||||
|
|
||||||
|
const allStdLearning = await models.StdLearning.findAll({
|
||||||
|
where: {
|
||||||
|
ID_LEVEL: {
|
||||||
|
[models.Sequelize.Op.in]: (
|
||||||
|
await models.Level.findAll({
|
||||||
|
where: { ID_TOPIC: ID_TOPIC },
|
||||||
|
attributes: ["ID_LEVEL"],
|
||||||
|
})
|
||||||
|
).map((level) => level.ID_LEVEL),
|
||||||
|
},
|
||||||
|
ID: {
|
||||||
|
[models.Sequelize.Op.in]: userIds,
|
||||||
|
},
|
||||||
|
STUDENT_FINISH: {
|
||||||
|
[models.Sequelize.Op.ne]: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Level,
|
||||||
|
as: "level",
|
||||||
|
attributes: ["NAME_LEVEL", "ID_TOPIC"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: models.User,
|
||||||
|
as: "learningUser",
|
||||||
|
attributes: ["NAME_USERS"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Student,
|
||||||
|
as: "students",
|
||||||
|
attributes: ["NISN"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attributes: [
|
||||||
|
"SCORE",
|
||||||
|
"FEEDBACK_STUDENT",
|
||||||
|
"STUDENT_START",
|
||||||
|
"STUDENT_FINISH",
|
||||||
|
],
|
||||||
|
order: [
|
||||||
|
[{ model: models.User, as: "learningUser" }, "NAME_USERS", "ASC"],
|
||||||
|
["STUDENT_FINISH", "DESC"],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedData = allStdLearning.map((stdLearning) => {
|
||||||
|
const level = stdLearning?.level;
|
||||||
|
const user = stdLearning?.learningUser;
|
||||||
|
const student = user?.students;
|
||||||
|
|
||||||
|
return {
|
||||||
|
NISN: student?.NISN,
|
||||||
|
NAME_USERS: user?.NAME_USERS,
|
||||||
|
NAME_LEVEL: level?.NAME_LEVEL,
|
||||||
|
SCORE: stdLearning?.SCORE,
|
||||||
|
FEEDBACK_STUDENT: stdLearning?.FEEDBACK_STUDENT,
|
||||||
|
STUDENT_START: stdLearning?.STUDENT_START,
|
||||||
|
STUDENT_FINISH: stdLearning?.STUDENT_FINISH,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const sanitizedClassName = className.replace(/\s+/g, "_");
|
||||||
|
const sanitizedTopicName = topicName.replace(/\s+/g, "_");
|
||||||
|
|
||||||
|
const tempDir = os.tmpdir();
|
||||||
|
const tempFilePath = path.join(
|
||||||
|
tempDir,
|
||||||
|
`Class_Monitoring_${sanitizedClassName}_${sanitizedTopicName}.csv`
|
||||||
|
);
|
||||||
|
|
||||||
|
const csvWriter = createObjectCsvWriter({
|
||||||
|
path: tempFilePath,
|
||||||
|
header: [
|
||||||
|
{ id: "field", title: "Field" },
|
||||||
|
{ id: "value", title: "Value" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const records = [
|
||||||
|
{ field: "Class Name", value: className },
|
||||||
|
{ field: "Section Name", value: sectionName },
|
||||||
|
{ field: "Topic Name", value: topicName },
|
||||||
|
];
|
||||||
|
|
||||||
|
await csvWriter.writeRecords(records);
|
||||||
|
|
||||||
|
const levelCsvWriter = createObjectCsvWriter({
|
||||||
|
path: tempFilePath,
|
||||||
|
append: true,
|
||||||
|
header: [
|
||||||
|
{ id: "NISN", title: "NISN" },
|
||||||
|
{ id: "NAME_USERS", title: "Student Name" },
|
||||||
|
{ id: "NAME_LEVEL", title: "Level Name" },
|
||||||
|
{ id: "SCORE", title: "Score" },
|
||||||
|
{ id: "FEEDBACK_STUDENT", title: "Student Feedback" },
|
||||||
|
{ id: "STUDENT_START", title: "Start Exercise" },
|
||||||
|
{ id: "STUDENT_FINISH", title: "Finish Exercise" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.appendFileSync(tempFilePath, "\n");
|
||||||
|
|
||||||
|
fs.appendFileSync(
|
||||||
|
tempFilePath,
|
||||||
|
"NISN,Student Name,Level Name,Score,Student Feedback,Start Exercise,Finish Exercise\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
await levelCsvWriter.writeRecords(formattedData);
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "text/csv");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename=Class_Monitoring_${sanitizedClassName}_${sanitizedTopicName}.csv`
|
||||||
|
);
|
||||||
|
|
||||||
|
const fileStream = fs.createReadStream(tempFilePath);
|
||||||
|
fileStream.pipe(res).on("finish", () => {
|
||||||
|
fs.unlinkSync(tempFilePath);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching monitoring data:", error);
|
||||||
|
response(500, null, "Error retrieving monitoring data!", res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
4
index.js
4
index.js
|
|
@ -9,7 +9,7 @@ dotenv.config();
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const corsOptions = {
|
const corsOptions = {
|
||||||
origin: "http://localhost:5173",
|
origin: `${process.env.CLIENT_URL}`,
|
||||||
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||||
allowedHeaders: ["Content-Type", "Authorization"],
|
allowedHeaders: ["Content-Type", "Authorization"],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
|
|
@ -27,6 +27,6 @@ app.use(express.static("public"));
|
||||||
app.listen(process.env.APP_PORT, () => {
|
app.listen(process.env.APP_PORT, () => {
|
||||||
testConnection();
|
testConnection();
|
||||||
console.log(
|
console.log(
|
||||||
`Server running on port http://localhost:${process.env.APP_PORT}`
|
`Server running on port ${process.env.APP_PORT}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"csv-writer": "^1.6.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
|
@ -646,6 +647,11 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/csv-writer": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g=="
|
||||||
|
},
|
||||||
"node_modules/d": {
|
"node_modules/d": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"csv-writer": "^1.6.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { getMonitorings, getMonitoringById,monitoringStudentsProgress, monitoringStudentProgressById, getClassMonitoringByClassId, getClassMonitoringDataByClassAndTopic, getMonitoringByTopicId, monitoringFeedback, monitoringFeedbackByClassAndTopic } from "../../controllers/monitoringControllers/monitoring.js";
|
import { getMonitorings, getMonitoringById,monitoringStudentsProgress, monitoringStudentProgressById, getClassMonitoringByClassId, getClassMonitoringDataByClassAndTopic, getMonitoringByTopicId, monitoringFeedback, monitoringFeedbackByClassAndTopic, monitoringStudentProgressCSVById, classMonitoringCSVByClassAndTopic } from "../../controllers/monitoringControllers/monitoring.js";
|
||||||
import { verifyLoginUser, adminOrTeacherOnly } from "../../middlewares/User/authUser.js";
|
import { verifyLoginUser, adminOrTeacherOnly } from "../../middlewares/User/authUser.js";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
@ -12,12 +12,16 @@ router.get("/monitoring/class", verifyLoginUser, getClassMonitoringDataByClassAn
|
||||||
|
|
||||||
router.get("/monitoring/:id", verifyLoginUser, getMonitoringById);
|
router.get("/monitoring/:id", verifyLoginUser, getMonitoringById);
|
||||||
|
|
||||||
|
router.get("/monitoring/class/csv", verifyLoginUser, adminOrTeacherOnly, classMonitoringCSVByClassAndTopic);
|
||||||
|
|
||||||
router.get("/monitoring/class/:classId", verifyLoginUser, adminOrTeacherOnly, getClassMonitoringByClassId);
|
router.get("/monitoring/class/:classId", verifyLoginUser, adminOrTeacherOnly, getClassMonitoringByClassId);
|
||||||
|
|
||||||
router.get("/monitoring/topic/:topicId", verifyLoginUser, getMonitoringByTopicId);
|
router.get("/monitoring/topic/:topicId", verifyLoginUser, getMonitoringByTopicId);
|
||||||
|
|
||||||
router.get("/monitoring/progress/:id", verifyLoginUser, adminOrTeacherOnly, monitoringStudentProgressById);
|
router.get("/monitoring/progress/:id", verifyLoginUser, adminOrTeacherOnly, monitoringStudentProgressById);
|
||||||
|
|
||||||
|
router.get("/monitoring/progress/csv/:id", verifyLoginUser, adminOrTeacherOnly, monitoringStudentProgressCSVById);
|
||||||
|
|
||||||
router.post("/monitoring/feedback/class", verifyLoginUser, adminOrTeacherOnly, monitoringFeedbackByClassAndTopic);
|
router.post("/monitoring/feedback/class", verifyLoginUser, adminOrTeacherOnly, monitoringFeedbackByClassAndTopic);
|
||||||
|
|
||||||
router.post("/monitoring/feedback/:id", verifyLoginUser, adminOrTeacherOnly, monitoringFeedback);
|
router.post("/monitoring/feedback/:id", verifyLoginUser, adminOrTeacherOnly, monitoringFeedback);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user