diff --git a/controllers/monitoringControllers/monitoring.js b/controllers/monitoringControllers/monitoring.js index 6c44dee..ba9c44b 100644 --- a/controllers/monitoringControllers/monitoring.js +++ b/controllers/monitoringControllers/monitoring.js @@ -207,7 +207,9 @@ export const monitoringStudentsProgress = async (req, res) => { })); if (sort === "nisn") { - formattedResult.sort((a, b) => a.NISN.localeCompare(b.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") { @@ -332,19 +334,11 @@ export const monitoringStudentProgressById = async (req, res) => { { FEEDBACK_STUDENT: { [models.Op.like]: `%${search}%` }, }, - // Search student_start dan student_finish belum bisa - { - [models.Sequelize.fn('DATE_FORMAT', models.Sequelize.col('STUDENT_START'), '%Y-%m-%d')]: { - [models.Op.like]: `%${search}%`, - }, - }, - { - [models.Sequelize.fn('DATE_FORMAT', models.Sequelize.col('STUDENT_FINISH'), '%Y-%m-%d')]: { - [models.Op.like]: `%${search}%`, - }, - }, ], }), + STUDENT_FINISH: { + [models.Sequelize.Op.ne]: null, + }, }, include: [ { @@ -575,3 +569,461 @@ export const getMonitoringByTopicId = async (req, res) => { 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( + 404, + 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", + ], + }); + + const sortedRecords = allStdLearning.sort((a, b) => { + if (sort === "nisn") { + return String(a.learningUser.students.NISN).localeCompare( + String(b.learningUser.students.NISN) + ); + } else if (sort === "user") { + return a.learningUser.NAME_USERS.localeCompare( + b.learningUser.NAME_USERS + ); + } 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") { + return a.FEEDBACK_STUDENT.localeCompare(b.FEEDBACK_STUDENT); + } else if (sort === "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); + }); + + 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: [], + }, + ], + }, + ], + }); + + console.log("Monitoring Data:", JSON.stringify(monitoringData, null, 2)); + + if (!monitoringData || monitoringData.length === 0) { + return response( + 404, + 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); + } +}; diff --git a/controllers/usersControllers/user.js b/controllers/usersControllers/user.js index 475a511..7a495f1 100644 --- a/controllers/usersControllers/user.js +++ b/controllers/usersControllers/user.js @@ -218,7 +218,9 @@ export const getStudents = async (req, res) => { })); if (sort === "nisn") { - formattedStudents.sort((a, b) => a.NISN.localeCompare(b.NISN)); + formattedStudents.sort((a, b) => + String(a.NISN).localeCompare(String(b.NISN)) + ); } else if (sort === "name") { formattedStudents.sort((a, b) => a.NAME_USERS.localeCompare(b.NAME_USERS) diff --git a/database/migrations/20241014070248-create-level.cjs b/database/migrations/20241014070248-create-level.cjs index 77602f8..c347a35 100644 --- a/database/migrations/20241014070248-create-level.cjs +++ b/database/migrations/20241014070248-create-level.cjs @@ -22,7 +22,7 @@ module.exports = { allowNull: false, }, CONTENT: { - type: Sequelize.STRING(1024), + type: Sequelize.TEXT('long'), allowNull: true, }, AUDIO: { diff --git a/models/contentModels/levelModel.js b/models/contentModels/levelModel.js index 9bea2cf..87cb93c 100644 --- a/models/contentModels/levelModel.js +++ b/models/contentModels/levelModel.js @@ -42,7 +42,7 @@ const LevelModel = (DataTypes) => { }, }, CONTENT: { - type: DataTypes.STRING(1024), + type: DataTypes.TEXT('long'), allowNull: true, }, AUDIO: { diff --git a/routes/monitoring/monitoring.js b/routes/monitoring/monitoring.js index 066f04f..ae6c127 100644 --- a/routes/monitoring/monitoring.js +++ b/routes/monitoring/monitoring.js @@ -1,5 +1,5 @@ import express from "express"; -import { getMonitorings, getMonitoringById,monitoringStudentsProgress, monitoringStudentProgressById, getMonitoringByTopicId, monitoringFeedback } from "../../controllers/monitoringControllers/monitoring.js"; +import { getMonitorings, getMonitoringById,monitoringStudentsProgress, monitoringStudentProgressById, getClassMonitoringByClassId, getClassMonitoringDataByClassAndTopic, getMonitoringByTopicId, monitoringFeedback, monitoringFeedbackByClassAndTopic } from "../../controllers/monitoringControllers/monitoring.js"; import { verifyLoginUser, adminOrTeacherOnly } from "../../middlewares/User/authUser.js"; const router = express.Router(); @@ -8,12 +8,18 @@ router.get("/monitoring", verifyLoginUser, getMonitorings); router.get("/monitoring/progress", verifyLoginUser, adminOrTeacherOnly, monitoringStudentsProgress); +router.get("/monitoring/class", verifyLoginUser, getClassMonitoringDataByClassAndTopic); + router.get("/monitoring/:id", verifyLoginUser, getMonitoringById); +router.get("/monitoring/class/:classId", verifyLoginUser, adminOrTeacherOnly, getClassMonitoringByClassId); + router.get("/monitoring/topic/:topicId", verifyLoginUser, getMonitoringByTopicId); router.get("/monitoring/progress/:id", verifyLoginUser, adminOrTeacherOnly, monitoringStudentProgressById); +router.post("/monitoring/feedback/class", verifyLoginUser, adminOrTeacherOnly, monitoringFeedbackByClassAndTopic); + router.post("/monitoring/feedback/:id", verifyLoginUser, adminOrTeacherOnly, monitoringFeedback); export default router \ No newline at end of file