Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions backend/src/controllers/attendance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { validationResult } from "express-validator";
import { Types } from "mongoose";

import { AttendanceModel } from "../models/attendance";
import { Section } from "../models/sections";
import { SessionModel } from "../models/session";

import type { RequestHandler } from "express";

Expand Down Expand Up @@ -54,6 +56,37 @@ export const updateAttendanceById: RequestHandler = async (req, res, next) => {
}
};

const getStudentsInSection = async (sectionId: Types.ObjectId): Promise<Types.ObjectId[]> => {
const section = await Section.findById(sectionId).select("enrolledStudents");

// Can't be null because of validation checks prior to calling this function
return section!.enrolledStudents;
};

export const ensureAttendanceForSession = async (sessionId: string) => {
const existing = await AttendanceModel.find({ session: sessionId });
if (existing.length > 0) return existing;

const session = await SessionModel.findById(sessionId);
if (!session) throw new Error("Session not found");

const sessionDate = new Date(session.sessionDate);
const today = new Date();
sessionDate.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
// Don't create attendance for future sessions
if (sessionDate > today) return [];

const students = await getStudentsInSection(session.section);
await Promise.all(
students.map(async (studentId) =>
AttendanceModel.create({ session: session._id, student: studentId, status: "PRESENT" }),
),
);

return AttendanceModel.find({ session: sessionId });
};

export const getAttendanceBySessionId: RequestHandler = async (req, res, next) => {
try {
const errors = validationResult(req);
Expand Down
36 changes: 6 additions & 30 deletions backend/src/controllers/session.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { validationResult } from "express-validator";

import { AttendanceModel } from "../models/attendance";
import { Section } from "../models/sections";
import { SessionModel } from "../models/session";

import type { RequestHandler } from "express";
import type { Types } from "mongoose";

const getStudentsInSection = async (sectionId: string): Promise<Types.ObjectId[]> => {
const section = await Section.findById(sectionId).select("enrolledStudents");
import { ensureAttendanceForSession } from "./attendance";

// Can't be null because of validation checks prior to calling this function
return section!.enrolledStudents;
};
import type { RequestHandler } from "express";

type CreateSessionBody = {
section: string;
Expand All @@ -30,22 +23,6 @@ export const createSession: RequestHandler = async (req, res, next) => {
sessionDate,
});

// create Attendance records for all students enrolled in Section

// Get all student Ids in the section (enrolledStudents list)
const students = await getStudentsInSection(section);

// Create attendance records for all students in session
await Promise.all(
students.map(async (studentId) =>
AttendanceModel.create({
session: session._id,
student: studentId,
status: "PRESENT",
}),
),
);

return res.status(201).json(session);
} catch (error) {
next(error);
Expand Down Expand Up @@ -87,14 +64,13 @@ export const getSession: RequestHandler = async (req, res, next) => {
return res.status(404).json({ error: "Session not found" });
}

// Check what we are searching for in the Attendance collection
const query = { session: id };
// Run the query
const attendanceRecords = await AttendanceModel.find(query).populate("student");
// This creates records if missing, checks the date, returns them
const attendanceRecords = await ensureAttendanceForSession(id);
const populated = await AttendanceModel.populate(attendanceRecords, { path: "student" });

const response = {
...session.toObject(),
attendees: attendanceRecords,
attendees: populated,
};

res.status(200).json(response);
Expand Down
18 changes: 11 additions & 7 deletions frontend/src/app/(pages)/attendance/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,12 @@ export default function Attendance() {
void load();
}, [activeSectionId]);

// Derive available dates from the loaded sessions for this section
// Derive available dates using local time
const availableDates = sessionList.map((s) => {
const d = new Date(s.sessionDate);
// Use UTC values to avoid timezone shift
const year = d.getUTCFullYear();
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
const day = String(d.getUTCDate()).padStart(2, "0");
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
});

Expand All @@ -88,8 +87,13 @@ export default function Attendance() {
if (!activeSectionId || !activeDate) return;

const match = sessionList.find((s) => {
const sDate = new Date(s.sessionDate).toISOString().split("T")[0];
return sDate === activeDate;
const d = new Date(s.sessionDate);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
const localSessionDate = `${year}-${month}-${day}`;

return localSessionDate === activeDate;
});

if (match) {
Expand Down
Loading