import response from "../../response.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) => { try { const monitoring = await models.Monitoring.findAll(); response(200, monitoring, "Success", res); } catch (error) { console.log(error); response(500, null, "Error retrieving monitoring data!", res); } }; export const getMonitoringById = async (req, res) => { try { const { id } = req.params; const monitoring = await models.Monitoring.findByPk(id); if (!monitoring) { return response(404, null, "Monitoring data not found", res); } response(200, monitoring, "Success", res); } catch (error) { console.log(error); response(500, null, "Internal Server Error", res); } }; export const createMonitoring = async (req) => { const { ID_STUDENT_LEARNING } = req.body; if (!req.user) { throw new Error("User not authenticated"); } if (!ID_STUDENT_LEARNING) { throw new Error("Student Learning ID is required"); } try { const existingMonitoring = await models.Monitoring.findOne({ where: { ID_STUDENT_LEARNING: ID_STUDENT_LEARNING }, }); if (existingMonitoring) { return existingMonitoring.toJSON(); } const stdLearning = await models.StdLearning.findByPk(ID_STUDENT_LEARNING); if (!stdLearning) { throw new Error("Student learning data not found"); } const userID = stdLearning.ID; const student = await models.Student.findOne({ where: { ID: userID } }); if (!student) { throw new Error("Student data not found"); } const { ID_SISWA } = student; const studentClass = await models.Student.findOne({ where: { ID_SISWA: ID_SISWA }, attributes: ["ID_CLASS"], }); const ID_CLASS = studentClass && studentClass.ID_CLASS ? studentClass.ID_CLASS : null; const newMonitoring = await models.Monitoring.create({ ID_STUDENT_LEARNING, ID_CLASS, }); return newMonitoring.toJSON(); } catch (error) { console.error(error); throw new Error("Internal Server Error"); } }; export const updateMonitoringClass = async ({ ID_STUDENT_LEARNING, ID_CLASS, }) => { if (!ID_STUDENT_LEARNING || !ID_CLASS) { throw new Error("Student Learning ID and Class ID are required"); } try { const monitoring = await models.Monitoring.findOne({ where: { ID_STUDENT_LEARNING }, }); if (!monitoring) { throw new Error("Monitoring data not found"); } monitoring.ID_CLASS = ID_CLASS; await monitoring.save(); return monitoring; } catch (error) { console.error(error); throw error; } }; export const monitoringStudentsProgress = async (req, res) => { const { page = 1, limit = 10, search = "", sort = "time" } = req.query; try { const { count, rows: monitorings } = await models.Monitoring.findAndCountAll({ include: [ { model: models.StdLearning, as: "stdLearningMonitoring", include: [ { model: models.User, as: "learningUser", attributes: ["NAME_USERS"], include: [ { model: models.Student, as: "students", attributes: ["NISN"], include: [ { model: models.Class, as: "studentClass", attributes: ["NAME_CLASS"], }, ], }, ], }, { model: models.Level, as: "level", attributes: ["NAME_LEVEL"], include: [ { model: models.Topic, as: "levelTopic", attributes: ["NAME_TOPIC"], include: [ { model: models.Section, as: "topicSection", attributes: ["NAME_SECTION"], }, ], }, ], }, ], }, ], where: { ...(search && { [models.Op.or]: [ { "$stdLearningMonitoring.learningUser.students.NISN$": { [models.Op.like]: `%${search}%`, }, }, { "$stdLearningMonitoring.learningUser.NAME_USERS$": { [models.Op.like]: `%${search}%`, }, }, { "$stdLearningMonitoring.level.levelTopic.topicSection.NAME_SECTION$": { [models.Op.like]: `%${search}%`, }, }, { "$stdLearningMonitoring.level.levelTopic.NAME_TOPIC$": { [models.Op.like]: `%${search}%`, }, }, ], }), }, distinct: true, }); const formattedResult = monitorings.map((monitoring) => ({ ID_MONITORING: monitoring.ID_MONITORING, NISN: monitoring.stdLearningMonitoring?.learningUser?.students?.NISN, NAME_USERS: monitoring.stdLearningMonitoring?.learningUser?.NAME_USERS, NAME_SECTION: monitoring.stdLearningMonitoring?.level?.levelTopic?.topicSection ?.NAME_SECTION, NAME_TOPIC: monitoring.stdLearningMonitoring?.level?.levelTopic?.NAME_TOPIC, NAME_CLASS: monitoring.stdLearningMonitoring?.learningUser?.students?.studentClass ?.NAME_CLASS ?? null, })); if (sort === "nisn") { formattedResult.sort((a, b) => String(a.NISN).localeCompare(String(b.NISN)) ); } else if (sort === "name") { formattedResult.sort((a, b) => a.NAME_USERS.localeCompare(b.NAME_USERS)); } else if (sort === "section") { formattedResult.sort((a, b) => a.NAME_SECTION.localeCompare(b.NAME_SECTION) ); } else if (sort === "topic") { formattedResult.sort((a, b) => a.NAME_TOPIC.localeCompare(b.NAME_TOPIC)); } else { formattedResult.sort((a, b) => new Date(b.TIME) - new Date(a.TIME)); } const paginatedResult = formattedResult.slice( (page - 1) * limit, page * limit ); const totalPages = Math.ceil(count / limit); const currentPage = parseInt(page); response( 200, { monitorings: paginatedResult, currentPage, totalPages, totalItems: count, }, "Success retrieving student monitoring progress!", res ); } catch (error) { console.error(error); response(500, null, "Error retrieving student monitoring progress!", res); } }; export const monitoringStudentProgressById = async (req, res) => { const { id } = req.params; const { page = 1, limit = 10, search = "", sort = "start" } = req.query; 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(200, null, "Monitoring data not found!", res); } const stdLearning = monitoring.stdLearningMonitoring; if (!stdLearning || stdLearning.length === 0) { return response(200, 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.findAndCountAll({ where: { ID: userID, ...(search && { [models.Op.or]: [ { "$level.NAME_LEVEL$": { [models.Op.like]: `%${search}%` }, }, { SCORE: { [models.Op.like]: `%${search}%` }, }, { FEEDBACK_STUDENT: { [models.Op.like]: `%${search}%` }, }, ], }), 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", "ASC"]], distinct: true, }); const levelArray = levels.rows .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, })) .sort((a, b) => { if (sort === "level") { return a.NAME_LEVEL.localeCompare(b.NAME_LEVEL); } else if (sort === "score") { return b.SCORE - a.SCORE; } else if (sort === "feedback") { 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") { return new Date(a.STUDENT_START) - new Date(b.STUDENT_START); } else if (sort === "finish") { return new Date(a.STUDENT_FINISH) - new Date(b.STUDENT_FINISH); } return 0; }); const paginatedResult = levelArray.slice((page - 1) * limit, page * limit); const totalPages = Math.ceil(levels.count / limit); const currentPage = parseInt(page); const result = { ID_MONITORING: monitoring.ID_MONITORING, NAME_SECTION: sectionName, NAME_TOPIC: topicName, NAME_USERS: studentName, NISN: nisn, levels: paginatedResult, currentPage, totalPages, totalItems: levels.count, }; response(200, result, "Success retrieving student progress!", res); } catch (error) { console.error(error); response(500, null, "Error retrieving student progress!", res); } }; export const monitoringFeedback = async (req, res) => { const { id } = req.params; const { feedback } = req.body; if (!req.user) { return response(401, null, "User not authenticated", res); } const { ID } = req.user; if (!ID) { return response(401, null, "Unauthorized: User ID not provided", res); } try { const teacher = await models.Teacher.findOne({ where: { ID: ID }, attributes: ["ID_GURU"], }); if (!teacher) { return response(404, null, "Teacher not found!", res); } await models.Monitoring.update( { ID_GURU: teacher.ID_GURU, FEEDBACK_GURU: feedback, }, { where: { ID_MONITORING: id }, returning: true, } ); const monitoringWithRelations = await models.Monitoring.findOne({ where: { ID_MONITORING: id }, include: [ { model: models.Teacher, as: "monitoringTeacher", attributes: ["ID_GURU"], include: [ { model: models.User, as: "teacherUser", attributes: ["NAME_USERS"], }, ], }, ], }); const result = { ID_MONITORING: monitoringWithRelations.ID_MONITORING, FEEDBACK_GURU: monitoringWithRelations.FEEDBACK_GURU, ID_GURU: monitoringWithRelations.monitoringTeacher?.ID_GURU, TEACHER_NAME: monitoringWithRelations.monitoringTeacher?.teacherUser?.NAME_USERS, }; response(200, result, "Success updating teacher feedback!", res); } catch (error) { console.error("Error in monitoringFeedback:", error); response(500, null, "Error updating teacher feedback!", res); } }; export const getMonitoringByTopicId = async (req, res) => { const { topicId } = req.params; if (!req.user) { return response(401, null, "User not authenticated", res); } const { ID } = req.user; if (!ID) { return response(401, null, "Unauthorized: User ID not provided", res); } try { const stdLearning = await models.StdLearning.findOne({ where: { ID: ID, }, include: [ { model: models.Level, as: "level", where: { ID_TOPIC: topicId }, attributes: ["ID_LEVEL", "NAME_LEVEL"], include: [ { model: models.Topic, as: "levelTopic", attributes: ["NAME_TOPIC"], }, ], }, ], }); if (!stdLearning) { return response( 200, null, "Student learning data not found for this topic!", res ); } const monitoringData = await models.Monitoring.findOne({ where: { ID_STUDENT_LEARNING: stdLearning.ID_STUDENT_LEARNING, }, include: [ { model: models.Teacher, as: "monitoringTeacher", attributes: ["ID_GURU"], include: [ { model: models.User, as: "teacherUser", attributes: ["NAME_USERS"], }, ], }, ], }); if (!monitoringData) { return response( 200, null, "Monitoring data not found for this learning record!", res ); } const result = { ID_MONITORING: monitoringData.ID_MONITORING, NAME_TOPIC: stdLearning.level?.levelTopic?.NAME_TOPIC, NAME_LEVEL: stdLearning.level?.NAME_LEVEL, TEACHER_NAME: monitoringData.monitoringTeacher?.teacherUser?.NAME_USERS, FEEDBACK_GURU: monitoringData.FEEDBACK_GURU, TIME_MONITORING: monitoringData.TIME_MONITORING, }; response(200, result, "Success retrieving monitoring data!", res); } catch (error) { console.error("Error in getMonitoringByTopicId:", error); response(500, null, "Error retrieving monitoring data!", res); } }; export const getClassMonitoringByClassId = async (req, res) => { const { classId } = req.params; const { page = 1, limit = 10, search = "", sort = "time" } = req.query; try { const searchCondition = search ? { [models.Op.or]: [ { "$stdLearningMonitoring.level.levelTopic.topicSection.NAME_SECTION$": { [models.Op.like]: `%${search}%` }, }, { "$stdLearningMonitoring.level.levelTopic.NAME_TOPIC$": { [models.Op.like]: `%${search}%`, }, }, ], } : {}; const { rows: monitoringRecords, count } = await models.Monitoring.findAndCountAll({ where: { ID_CLASS: classId, ...searchCondition }, include: [ { model: models.StdLearning, as: "stdLearningMonitoring", include: [ { model: models.Level, as: "level", attributes: ["ID_LEVEL"], include: [ { model: models.Topic, as: "levelTopic", attributes: ["ID_TOPIC", "NAME_TOPIC"], include: [ { model: models.Section, as: "topicSection", attributes: ["ID_SECTION", "NAME_SECTION"], }, ], }, ], }, ], }, ], distinct: true, }); if (!monitoringRecords || monitoringRecords.length === 0) { return response( 200, null, "No monitoring data found for this class!", res ); } const uniqueRecords = new Map(); monitoringRecords.forEach((monitoring) => { const sectionId = monitoring.stdLearningMonitoring?.level?.levelTopic?.topicSection ?.ID_SECTION; const sectionName = monitoring.stdLearningMonitoring?.level?.levelTopic?.topicSection ?.NAME_SECTION; const topicId = monitoring.stdLearningMonitoring?.level?.levelTopic?.ID_TOPIC; const topicName = monitoring.stdLearningMonitoring?.level?.levelTopic?.NAME_TOPIC; const key = `${sectionId}-${topicId}`; if (!uniqueRecords.has(key)) { uniqueRecords.set(key, { ID_SECTION: sectionId, ID_TOPIC: topicId, NAME_SECTION: sectionName, NAME_TOPIC: topicName, }); } }); const sortedRecords = Array.from(uniqueRecords.values()); if (sort === "section") { sortedRecords.sort((a, b) => a.NAME_SECTION.localeCompare(b.NAME_SECTION) ); } else if (sort === "topic") { sortedRecords.sort((a, b) => a.NAME_TOPIC.localeCompare(b.NAME_TOPIC)); } const paginatedRecords = sortedRecords.slice( (page - 1) * limit, page * limit ); const totalPages = Math.ceil(uniqueRecords.size / limit); const paginatedResult = { data: paginatedRecords, currentPage: parseInt(page), totalPages, totalItems: uniqueRecords.size, }; response( 200, paginatedResult, "Success retrieving unique monitoring section and topic data!", res ); } catch (error) { console.error("Error in getClassMonitoringByClassId:", error); response( 500, null, "Error retrieving monitoring section and topic data!", res ); } }; export const getClassMonitoringDataByClassAndTopic = async (req, res) => { const { ID_CLASS, ID_TOPIC } = req.body; const { page = 1, limit = 10, search = "", sort = "finish" } = req.query; 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 classId = classData.ID_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 topicId = topicData.ID_TOPIC; const sectionName = topicData.topicSection?.NAME_SECTION; const sectionId = topicData.topicSection?.ID_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: classId, ID_SECTION: sectionId, ID_TOPIC: topicId, 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 searchCondition = search ? { [models.Sequelize.Op.or]: [ { "$learningUser.NAME_USERS$": { [models.Sequelize.Op.like]: `%${search}%`, }, }, { "$learningUser.students.NISN$": { [models.Sequelize.Op.like]: `%${search}%`, }, }, { "$level.NAME_LEVEL$": { [models.Sequelize.Op.like]: `%${search}%`, }, }, { $SCORE$: { [models.Sequelize.Op.like]: `%${search}%` } }, { $FEEDBACK_STUDENT$: { [models.Sequelize.Op.like]: `%${search}%` }, }, ], } : {}; const { rows: allStdLearning, count } = await models.StdLearning.findAndCountAll({ 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, }, ...searchCondition, }, 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: [["STUDENT_FINISH", "DESC"]], }); 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") { return String(a.learningUser.students.NISN).localeCompare( String(b.learningUser.students.NISN) ); } else if (sort === "name") { return userComparison; } else if (sort === "level") { return a.level.NAME_LEVEL.localeCompare(b.level.NAME_LEVEL); } else if (sort === "score") { return b.SCORE - a.SCORE; } else if (sort === "feedback") { 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") { return new Date(a.STUDENT_START) - new Date(b.STUDENT_START); } return new Date(a.STUDENT_FINISH) - new Date(b.STUDENT_FINISH); }); const paginatedRecords = sortedRecords.slice( (page - 1) * limit, page * limit ); const totalPages = Math.ceil(count / limit); const formattedData = paginatedRecords.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 result = { ID_CLASS: classId, ID_SECTION: sectionId, ID_TOPIC: topicId, NAME_CLASS: className, NAME_SECTION: sectionName, NAME_TOPIC: topicName, levels: formattedData, currentPage: parseInt(page), totalPages, totalItems: count, }; response(200, result, "Success retrieving monitoring data!", res); } catch (error) { console.error("Error fetching monitoring data:", error); response(500, null, "Error retrieving monitoring data!", res); } }; export const monitoringFeedbackByClassAndTopic = async (req, res) => { const { ID_CLASS, ID_TOPIC, FEEDBACK } = req.body; if (!req.user) { return response(401, null, "User not authenticated", res); } const { ID } = req.user; if (!ID) { return response(401, null, "Unauthorized: User ID not provided", res); } try { const teacher = await models.Teacher.findOne({ where: { ID }, attributes: ["ID_GURU"], }); if (!teacher) { return response(404, null, "Teacher not found!", res); } const monitoringData = await models.Monitoring.findAll({ where: { ID_CLASS }, include: [ { model: models.StdLearning, as: "stdLearningMonitoring", required: true, include: [ { model: models.Level, as: "level", where: { ID_TOPIC }, attributes: [], }, ], }, ], }); if (!monitoringData || monitoringData.length === 0) { return response( 200, null, "No monitoring data found for this class and topic!", res ); } for (const monitoring of monitoringData) { const [updatedRows] = await models.Monitoring.update( { ID_GURU: teacher.ID_GURU, FEEDBACK_GURU: FEEDBACK, }, { where: { ID_MONITORING: monitoring.ID_MONITORING }, } ); } const updatedMonitoringData = await models.Monitoring.findAll({ where: { ID_CLASS: ID_CLASS }, include: [ { model: models.Teacher, as: "monitoringTeacher", attributes: ["ID_GURU"], include: [ { model: models.User, as: "teacherUser", attributes: ["NAME_USERS"], }, ], }, { model: models.StdLearning, as: "stdLearningMonitoring", required: true, include: [ { model: models.Level, as: "level", where: { ID_TOPIC }, }, ], }, ], }); const result = updatedMonitoringData.map((monitoring) => ({ ID_MONITORING: monitoring.ID_MONITORING, FEEDBACK_GURU: monitoring.FEEDBACK_GURU, ID_GURU: monitoring.monitoringTeacher?.ID_GURU, TEACHER_NAME: monitoring.monitoringTeacher?.teacherUser?.NAME_USERS, })); response( 200, result, "Success updating teacher feedback for class and topic!", res ); } catch (error) { console.error("Error in monitoringFeedbackByClassAndTopic:", error); 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(200, 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); } };