2024-09-13 13:03:35 +00:00
|
|
|
import response from "../../response.js";
|
|
|
|
|
import models from "../../models/index.js";
|
2024-10-28 02:36:05 +00:00
|
|
|
import { createObjectCsvWriter } from "csv-writer";
|
|
|
|
|
import { Readable } from "stream";
|
|
|
|
|
import fs from "fs";
|
|
|
|
|
import os from "os";
|
|
|
|
|
import path from "path";
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
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) => {
|
2024-10-14 01:23:37 +00:00
|
|
|
const { page = 1, limit = 10, search = "", sort = "time" } = req.query;
|
|
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
try {
|
2024-10-14 01:23:37 +00:00
|
|
|
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}%`,
|
2024-09-13 13:03:35 +00:00
|
|
|
},
|
2024-10-14 01:23:37 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"$stdLearningMonitoring.learningUser.NAME_USERS$": {
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
2024-09-13 13:03:35 +00:00
|
|
|
},
|
2024-10-14 01:23:37 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"$stdLearningMonitoring.level.levelTopic.topicSection.NAME_SECTION$":
|
|
|
|
|
{
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"$stdLearningMonitoring.level.levelTopic.NAME_TOPIC$": {
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
2024-09-13 13:03:35 +00:00
|
|
|
},
|
2024-10-14 01:23:37 +00:00
|
|
|
distinct: true,
|
|
|
|
|
});
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-10-14 01:23:37 +00:00
|
|
|
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") {
|
2024-10-23 04:09:33 +00:00
|
|
|
formattedResult.sort((a, b) =>
|
|
|
|
|
String(a.NISN).localeCompare(String(b.NISN))
|
|
|
|
|
);
|
2024-10-14 01:23:37 +00:00
|
|
|
} 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);
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
response(
|
|
|
|
|
200,
|
2024-10-14 01:23:37 +00:00
|
|
|
{
|
|
|
|
|
monitorings: paginatedResult,
|
|
|
|
|
currentPage,
|
|
|
|
|
totalPages,
|
|
|
|
|
totalItems: count,
|
|
|
|
|
},
|
2024-09-13 13:03:35 +00:00
|
|
|
"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;
|
2024-10-14 01:23:37 +00:00
|
|
|
const { page = 1, limit = 10, search = "", sort = "start" } = req.query;
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
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"],
|
2024-10-14 01:23:37 +00:00
|
|
|
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"],
|
|
|
|
|
},
|
|
|
|
|
],
|
2024-09-13 13:03:35 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!monitoring) {
|
2024-12-02 07:27:15 +00:00
|
|
|
return response(200, null, "Monitoring data not found!", res);
|
2024-09-13 13:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const stdLearning = monitoring.stdLearningMonitoring;
|
|
|
|
|
|
|
|
|
|
if (!stdLearning || stdLearning.length === 0) {
|
2024-12-02 07:27:15 +00:00
|
|
|
return response(200, null, "No student learning data found!", res);
|
2024-09-13 13:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-02 06:11:59 +00:00
|
|
|
const userID = stdLearning.ID;
|
|
|
|
|
const topicID = stdLearning.level.ID_TOPIC;
|
2024-10-14 01:23:37 +00:00
|
|
|
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;
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-10-14 01:23:37 +00:00
|
|
|
const levels = await models.StdLearning.findAndCountAll({
|
2024-09-13 13:03:35 +00:00
|
|
|
where: {
|
2024-10-02 06:11:59 +00:00
|
|
|
ID: userID,
|
2024-10-14 01:23:37 +00:00
|
|
|
...(search && {
|
|
|
|
|
[models.Op.or]: [
|
|
|
|
|
{
|
|
|
|
|
"$level.NAME_LEVEL$": { [models.Op.like]: `%${search}%` },
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
SCORE: { [models.Op.like]: `%${search}%` },
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
FEEDBACK_STUDENT: { [models.Op.like]: `%${search}%` },
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
2024-10-23 04:09:33 +00:00
|
|
|
STUDENT_FINISH: {
|
|
|
|
|
[models.Sequelize.Op.ne]: null,
|
|
|
|
|
},
|
2024-09-13 13:03:35 +00:00
|
|
|
},
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Level,
|
|
|
|
|
as: "level",
|
|
|
|
|
attributes: ["NAME_LEVEL", "ID_TOPIC"],
|
2024-10-02 06:11:59 +00:00
|
|
|
where: {
|
|
|
|
|
ID_TOPIC: topicID,
|
|
|
|
|
},
|
2024-09-13 13:03:35 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
attributes: [
|
|
|
|
|
"SCORE",
|
|
|
|
|
"FEEDBACK_STUDENT",
|
|
|
|
|
"STUDENT_START",
|
|
|
|
|
"STUDENT_FINISH",
|
|
|
|
|
],
|
2024-10-14 01:23:37 +00:00
|
|
|
distinct: true,
|
2024-09-13 13:03:35 +00:00
|
|
|
});
|
|
|
|
|
|
2024-10-14 01:23:37 +00:00
|
|
|
const levelArray = levels.rows
|
2024-10-02 06:11:59 +00:00
|
|
|
.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) => {
|
2024-10-14 01:23:37 +00:00
|
|
|
if (sort === "level") {
|
|
|
|
|
return a.NAME_LEVEL.localeCompare(b.NAME_LEVEL);
|
|
|
|
|
} else if (sort === "score") {
|
|
|
|
|
return b.SCORE - a.SCORE;
|
|
|
|
|
} else if (sort === "feedback") {
|
2024-10-28 02:36:05 +00:00
|
|
|
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);
|
|
|
|
|
}
|
2024-10-14 01:23:37 +00:00
|
|
|
} 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;
|
2024-10-02 06:11:59 +00:00
|
|
|
});
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-10-14 01:23:37 +00:00
|
|
|
const paginatedResult = levelArray.slice((page - 1) * limit, page * limit);
|
|
|
|
|
const totalPages = Math.ceil(levels.count / limit);
|
|
|
|
|
const currentPage = parseInt(page);
|
|
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
const result = {
|
|
|
|
|
ID_MONITORING: monitoring.ID_MONITORING,
|
2024-10-14 01:23:37 +00:00
|
|
|
NAME_SECTION: sectionName,
|
|
|
|
|
NAME_TOPIC: topicName,
|
|
|
|
|
NAME_USERS: studentName,
|
|
|
|
|
NISN: nisn,
|
|
|
|
|
levels: paginatedResult,
|
|
|
|
|
currentPage,
|
|
|
|
|
totalPages,
|
|
|
|
|
totalItems: levels.count,
|
2024-09-13 13:03:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 07:27:15 +00:00
|
|
|
await models.Monitoring.update(
|
2024-09-13 13:03:35 +00:00
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-10-02 06:11:59 +00:00
|
|
|
|
|
|
|
|
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(
|
2024-12-02 07:27:15 +00:00
|
|
|
200,
|
2024-10-02 06:11:59 +00:00
|
|
|
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(
|
2024-12-02 07:27:15 +00:00
|
|
|
200,
|
2024-10-02 06:11:59 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-10-23 04:09:33 +00:00
|
|
|
|
|
|
|
|
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(
|
2024-12-02 07:27:15 +00:00
|
|
|
200,
|
2024-10-23 04:09:33 +00:00
|
|
|
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",
|
|
|
|
|
],
|
2024-10-28 02:36:05 +00:00
|
|
|
order: [["STUDENT_FINISH", "DESC"]],
|
2024-10-23 04:09:33 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const sortedRecords = allStdLearning.sort((a, b) => {
|
2024-10-28 02:36:05 +00:00
|
|
|
const userComparison = a.learningUser.NAME_USERS.localeCompare(
|
|
|
|
|
b.learningUser.NAME_USERS
|
|
|
|
|
);
|
|
|
|
|
if (userComparison !== 0) {
|
|
|
|
|
return userComparison;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-23 04:09:33 +00:00
|
|
|
if (sort === "nisn") {
|
|
|
|
|
return String(a.learningUser.students.NISN).localeCompare(
|
|
|
|
|
String(b.learningUser.students.NISN)
|
|
|
|
|
);
|
2024-10-28 02:36:05 +00:00
|
|
|
} else if (sort === "name") {
|
|
|
|
|
return userComparison;
|
2024-10-23 04:09:33 +00:00
|
|
|
} 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") {
|
2024-10-28 02:36:05 +00:00
|
|
|
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);
|
|
|
|
|
}
|
2024-10-23 04:09:33 +00:00
|
|
|
} else if (sort === "start") {
|
|
|
|
|
return new Date(a.STUDENT_START) - new Date(b.STUDENT_START);
|
|
|
|
|
}
|
2024-10-28 02:36:05 +00:00
|
|
|
|
2024-10-23 04:09:33 +00:00
|
|
|
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(
|
2024-12-02 07:27:15 +00:00
|
|
|
200,
|
2024-10-23 04:09:33 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-10-28 02:36:05 +00:00
|
|
|
|
|
|
|
|
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) {
|
2024-12-02 07:27:15 +00:00
|
|
|
return response(200, null, "No student learning data found!", res);
|
2024-10-28 02:36:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|