diff --git a/package-lock.json b/package-lock.json index 4c08788b..18d5180f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@graphql-tools/utils": "^10.0.4", "@octokit/graphql": "^7.0.1", "@octokit/rest": "^19.0.13", + "@types/node-cron": "^3.0.11", "apollo-server": "^3.13.0", "bcryptjs": "^2.4.3", "cloudinary": "^1.30.1", @@ -34,6 +35,7 @@ "jsonwebtoken": "^9.0.2", "mongodb": "^4.17.1", "mongoose": "^6.6.1", + "node-cron": "^3.0.3", "node-fetch": "^2.6.12", "nodemailer": "^6.7.8", "normalize-mongoose": "^1.0.0", @@ -3066,6 +3068,12 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "license": "MIT" + }, "node_modules/@types/node-fetch": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", @@ -6780,6 +6788,27 @@ "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-cron/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", diff --git a/package.json b/package.json index 52abb3b9..2db1a19b 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@graphql-tools/utils": "^10.0.4", "@octokit/graphql": "^7.0.1", "@octokit/rest": "^19.0.13", + "@types/node-cron": "^3.0.11", "apollo-server": "^3.13.0", "bcryptjs": "^2.4.3", "cloudinary": "^1.30.1", @@ -78,6 +79,7 @@ "jsonwebtoken": "^9.0.2", "mongodb": "^4.17.1", "mongoose": "^6.6.1", + "node-cron": "^3.0.3", "node-fetch": "^2.6.12", "nodemailer": "^6.7.8", "normalize-mongoose": "^1.0.0", diff --git a/src/index.ts b/src/index.ts index db069f28..9ad3fabe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,6 +50,7 @@ import { IResolvers } from '@graphql-tools/utils' import invitationSchema from './schema/invitation.schema' import TableViewInvitationResolver from './resolvers/TableViewInvitationResolver' import eventSchema from './schema/event.schema' +import './utils/cron-jobs/team-jobs' const PORT: number = parseInt(process.env.PORT!) || 4000 diff --git a/src/models/attendance.model.ts b/src/models/attendance.model.ts index d869f949..f1417e2b 100644 --- a/src/models/attendance.model.ts +++ b/src/models/attendance.model.ts @@ -18,8 +18,20 @@ const AttendanceSchema = new Schema({ teams: [ { + date: { + type: Date, + required: false, + default: () => { + const date = new Date(); + if (date.getUTCHours() >= 22) { + date.setUTCDate(date.getUTCDate() + 1); + date.setUTCHours(0, 0, 0, 0); + } + return date + }, + }, team: { - type: mongoose.Types.ObjectId, + type: mongoose.Schema.Types.ObjectId, ref: 'Team', required: true }, @@ -34,24 +46,33 @@ const AttendanceSchema = new Schema({ day: { type: String, enum: ['mon', 'tue', 'wed', 'thu', 'fri'], - required: true + required: true }, date: { type: Date, - required: true + required: true }, score: { type: String, - enum: ['0', '1', '2'], - required: true + enum: [0, 1, 2], + required: true }, }, ], }, - ],} + ], + } ], - -}) +}, { timestamps: true }) + +AttendanceSchema.index( + { + phase: 1, + cohort: 1, + createdAt: 1 + }, + { unique: true } +); const Attendance = mongoose.model('Attendance', AttendanceSchema) export { Attendance } diff --git a/src/models/team.model.ts b/src/models/team.model.ts index b4af4170..5c140f02 100644 --- a/src/models/team.model.ts +++ b/src/models/team.model.ts @@ -1,10 +1,13 @@ import mongoose, { Schema } from 'mongoose' import { User } from './user' import { CohortInterface } from './cohort.model'; +import { PhaseInterface } from './phase.model'; export interface TeamInterface { + _id: mongoose.Types.ObjectId; name: string; cohort?: CohortInterface; + phase?: PhaseInterface; ttl?: mongoose.Types.ObjectId; members: mongoose.Types.ObjectId[]; startingPhase: Date; @@ -57,6 +60,11 @@ const teamSchema = new Schema( type: mongoose.Types.ObjectId, ref: 'Program', }, + isJobActive: { + type: Boolean, + default: true, + required: true, + }, }, { statics: { diff --git a/src/models/user.ts b/src/models/user.ts index bc59a9ed..3eee3ae1 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -9,17 +9,19 @@ export interface UserStatus { } export interface UserInterface { - _id: mongoose.Types.ObjectId - email: string - password: string - role: string - team?: mongoose.Types.ObjectId - status: UserStatus - cohort?: mongoose.Types.ObjectId - program?: mongoose.Types.ObjectId - organizations: string[] - pushNotifications: boolean - emailNotifications: boolean + _id: mongoose.Types.ObjectId; + email: string; + password: string; + role: string; + team?: mongoose.Types.ObjectId; + status: UserStatus; + cohort?: mongoose.Types.ObjectId; + program?: mongoose.Types.ObjectId; + organizations: string[]; + pushNotifications: boolean; + emailNotifications: boolean; + profile?: mongoose.Types.ObjectId; + ratings?: mongoose.Types.ObjectId[]; } export enum RoleOfUser { diff --git a/src/resolvers/attendance.resolvers.ts b/src/resolvers/attendance.resolvers.ts index 4573e09c..56753b6a 100644 --- a/src/resolvers/attendance.resolvers.ts +++ b/src/resolvers/attendance.resolvers.ts @@ -2,73 +2,268 @@ import { Attendance } from '../models/attendance.model' import { IntegerType, ObjectId } from 'mongodb' import { Context } from './../context' -import mongoose, { Error, Types } from 'mongoose' +import mongoose, { Document, Error, Types } from 'mongoose' import { checkUserLoggedIn } from '../helpers/user.helpers' import { pushNotification } from '../utils/notification/pushNotification' -import Phase from '../models/phase.model' +import Phase, { PhaseInterface } from '../models/phase.model' import { RoleOfUser, User, UserInterface } from '../models/user' -import Team from '../models/team.model' +import Team, { TeamInterface } from '../models/team.model' import { CohortInterface } from '../models/cohort.model' import { GraphQLError } from 'graphql' import { checkLoggedInOrganization } from '../helpers/organization.helper' +import { getDateForDays } from '../utils/getDateForDays' +import { format, isSameWeek } from 'date-fns' +import { id } from 'date-fns/locale' +import { AnyAaaaRecord } from 'dns' +import { addNewAttendanceWeek } from '../utils/cron-jobs/team-jobs' interface TraineeAttendanceStatus { day: 'mon' | 'tue' | 'wed' | 'thu' | 'fri' - score: '0' | '1' | '2' + date: string + score: 0 | 1 | 2 } interface TraineeAttendanceData { trainee: ObjectId - status: TraineeAttendanceStatus + score: number } +interface TeamAttendanceData { + week: number + phase: PhaseInterface + cohort: CohortInterface + teams: Array<{ + team: TeamInterface + date: string + trainees: Array<{ + trainee: UserInterface + status: Array + } + > + }> + createdAt: string +} + interface AttendanceInput { week: string + day: 'mon' | 'tue' | 'wed' | 'thu' | 'fri' team: string - date?: string phase?: string + today: boolean + yesterday: boolean trainees: TraineeAttendanceData[] orgToken: string } +interface TraineeAttendanceDataInterface { + trainee?: { + id: string; + email: string; + status: { + status: string + }; + profile: { + name: string; + }; + }; + score?: number; +} +interface DayInterface { + date: string + isValid: boolean +} +export interface WeekdaysInterface { + mon: DayInterface; + tue: DayInterface; + wed: DayInterface; + thu: DayInterface; + fri: DayInterface; +} +interface TraineeAttendanceDayInterface { + week: number; + phase: { + id: string + name: string + }; + dates: WeekdaysInterface; + days: { + mon: TraineeAttendanceDataInterface[]; + tue: TraineeAttendanceDataInterface[]; + wed: TraineeAttendanceDataInterface[]; + thu: TraineeAttendanceDataInterface[]; + fri: TraineeAttendanceDataInterface[]; + }; +} + +interface AttendanceWeeksInterface { + phase: { + name: string, + id: string + } + weeks: Array +} + +const formatAttendanceData = (data: TeamAttendanceData[], teamData: TeamInterface) => { + const tempPhases: PhaseInterface[] = []; + const attendanceWeeks: AttendanceWeeksInterface[] = [] + const phase = { + id: teamData.phase?._id.toString() || teamData.cohort!.phase._id.toString(), + name: teamData.phase?.name || teamData.cohort!.phase.name + } + + const attendanceResult: TraineeAttendanceDayInterface[] = []; + data.forEach(attendance => { + const result: TraineeAttendanceDayInterface = { + week: attendance.week, + dates: { + mon: { + date: '', + isValid: false + }, + tue: { + date: '', + isValid: false + }, + wed: { + date: '', + isValid: false + }, + thu: { + date: '', + isValid: false + }, + fri: { + date: '', + isValid: false + }, + }, + phase: { + id: attendance.phase._id.toString(), + name: attendance.phase.name + }, + days: { + mon: [], + tue: [], + wed: [], + thu: [], + fri: [], + }, + }; + + // Store all attendance weeks + let isWeekSet = false + attendanceWeeks.forEach((week, index) => { + if (week.phase.id === attendance.phase._id.toString()) { + isWeekSet = true; + attendanceWeeks[index].weeks.push(attendance.week) + } + }) + + !isWeekSet && attendanceWeeks.push({ + phase: { + id: attendance.phase._id.toString(), + name: attendance.phase.name + }, + weeks: [attendance.week] + }) + + if (!tempPhases.find((p) => p._id.equals(attendance.phase._id))) + tempPhases.push(attendance.phase); + let date = attendance.teams[0].date; + + attendance.teams[0].trainees.forEach((traineeData) => { + if (traineeData.status.length && traineeData.trainee.status.status !== 'drop') { + traineeData.status.forEach((traineeStatus) => { + if (traineeStatus.date && !date) { + date = traineeStatus.date; + } + + result.days[ + traineeStatus.day as 'mon' | 'tue' | 'wed' | 'thu' | 'fri' + ].push({ + trainee: { + ...(traineeData.trainee as unknown as Document).toObject(), + profile: { + name: (traineeData.trainee.profile! as any).name + }, + id: traineeData.trainee._id.toString(), + }, + score: traineeStatus.score, + }); + }); + } + }); + result.dates = getDateForDays(date); + attendanceResult.push(result); + }); + + + const today = new Date(); + if (today.getUTCHours() >= 22) { + today.setUTCDate(today.getUTCDate() + 1); + today.setUTCHours(0, 0, 0, 0); + } + + const yesterday = new Date(new Date().getDay() === 1 ? new Date().setDate(new Date().getDate() - 3) : new Date().setDate(new Date().getDate() - 1)); + if (yesterday.getUTCHours() >= 22) { + yesterday.setUTCDate(yesterday.getUTCDate() + 1); + yesterday.setUTCHours(0, 0, 0, 0); + } + return { attendanceWeeks, attendance: attendanceResult, today, yesterday } +}; + const validateAttendance = async ( team: string, orgToken: string, trainees: TraineeAttendanceData[], - context: Context + context: Context, + isUpdating = false ) => { const org = await checkLoggedInOrganization(orgToken) if (!org) { - throw new Error("Orgnasation doesn't exist") + throw new Error('Organisation doesn\'t exist') } - ;(await checkUserLoggedIn(context))(['coordinator']) + const { userId }: any = (await checkUserLoggedIn(context))(['coordinator', 'ttl']) const teamData = await Team.findById(team) + .populate({ + path: 'members', + match: { role: 'trainee' } + }) .populate('cohort') + .populate('phase') .populate('cohort.phase') if (!teamData) { - throw new Error("Team provided doesn't exist") + throw new Error('Team provided doesn\'t exist') } + const phaseData = await Phase.findById( - (teamData.cohort as CohortInterface).phase._id + teamData.phase || (teamData.cohort as CohortInterface).phase._id ) if (!phaseData) { - throw new Error("Phase provided doesn't exist") + throw new Error('Phase provided doesn\'t exist') } - trainees.forEach((trainee) => { - if ( - trainee.status.day.toLowerCase() !== trainees[0].status.day.toLowerCase() - ) { - throw new GraphQLError( - 'Please make sure, you submit same date for each trainee ', - { + teamData.members.forEach((member) => { + const trainee = member as UserInterface; + if (trainee.role === 'trainee' && trainee.status.status === 'active') { + const sentTestTrainee = trainees.find(traineeData => trainee._id.equals(traineeData.trainee)) + if (!sentTestTrainee && !isUpdating) { + throw new GraphQLError('Please ensure attendance is taken for all active trainees in the team', { extensions: { - code: 'INCONSISTENT_TRAINEE_ATTENDANCE_DATE', + code: 'INCONSISTENT_TRAINEE_ATTENDANCE', }, - } - ) + }) + } + if (sentTestTrainee && ![0, 1, 2].includes(sentTestTrainee.score)) { + throw new GraphQLError('Attendance cannot be recorded due to an invalid score for one of trainees.', { + extensions: { + code: 'INVALID_TRAINEE_SCORE', + }, + }) + } } }) return { teamData, phaseData, + userId } } @@ -77,16 +272,21 @@ const returnAttendanceData = async (teamData: any) => { .populate('phase') .populate('cohort') .populate('teams.team') - .populate('teams.trainees.trainee', '-password') + .populate({ + path: 'teams.trainees.trainee', + select: '-password', + populate: { + path: 'profile', + } + }) const sanitizedAttendance: any[] = [] attendances.forEach((attendance) => { + const result = attendance.teams.find((teamAttendanceData) => (teamAttendanceData.team as ObjectId).equals(teamData.id) ) - const filteredTrainees = result?.trainees.filter( - (trainee) => (trainee.trainee as UserInterface).status.status !== 'drop' - ) + const filteredTrainees = result?.trainees.filter(trainee => (trainee.trainee as UserInterface).status.status !== 'drop') result && sanitizedAttendance.push({ @@ -100,18 +300,10 @@ const returnAttendanceData = async (teamData: any) => { ...(attendance.phase as mongoose.Document).toObject(), id: (attendance.phase as mongoose.Document)._id, }, - teams: [ - { - team: { - ...(result.team as mongoose.Document).toObject(), - id: (result.team as mongoose.Document)._id, - }, - trainees: filteredTrainees, - }, - ], + teams: [{ date: result.date, team: { ...(result.team as unknown as mongoose.Document).toObject(), id: (result.team as unknown as mongoose.Document)._id }, trainees: filteredTrainees }], }) }) - return sanitizedAttendance + return formatAttendanceData(sanitizedAttendance, teamData) } const attendanceResolver = { @@ -121,7 +313,7 @@ const attendanceResolver = { { traineeEmail }: any, context: Context ) { - ;(await checkUserLoggedIn(context))([RoleOfUser.TRAINEE]) + ; (await checkUserLoggedIn(context))([RoleOfUser.TRAINEE]) const attendance = await Attendance.find() const weeklyAttendance = attendance.map((week: any) => { @@ -140,25 +332,29 @@ const attendanceResolver = { { team }: { team: string }, context: Context ) { - ;(await checkUserLoggedIn(context))([RoleOfUser.COORDINATOR]) - const { userId } = (await checkUserLoggedIn(context))([ - RoleOfUser.COORDINATOR, - ]) + (await checkUserLoggedIn(context))([RoleOfUser.COORDINATOR, RoleOfUser.TTL]) + + await addNewAttendanceWeek(); const teamData = await Team.findById(team) + .populate('phase') + .populate({ + path: 'cohort', + populate: { + path: 'phase' + } + }) if (!teamData) { - throw new Error("Team provided doesn't exist") + throw new Error('Team provided doesn\'t exist') } return returnAttendanceData(teamData) }, async getAttendanceStats(_: any, args: any, context: Context) { - ;(await checkUserLoggedIn(context))([RoleOfUser.COORDINATOR]) - const { userId } = (await checkUserLoggedIn(context))([ - RoleOfUser.COORDINATOR, - ]) + ; (await checkUserLoggedIn(context))([RoleOfUser.COORDINATOR]) + const { userId } = (await checkUserLoggedIn(context))([RoleOfUser.COORDINATOR]) const attendances: any = await Attendance.find({ coordinatorId: userId }) //calculate statistic @@ -213,17 +409,68 @@ const attendanceResolver = { }, Mutation: { + async pauseAndResumeTeamAttendance(_: any, { orgToken, team }: { orgToken: string, team: string }, context: Context) { + (await checkUserLoggedIn(context))(['coordinator', 'ttl']); + + const teamData = await Team.findById(team).populate({ + path: 'members', + match: { role: 'trainee' } + }) + .populate('cohort') + .populate('phase') + .populate('cohort.phase'); + + if (!teamData) { + throw new Error('Team provided doesn\'t exist') + } + const tempIsJobActive = teamData.isJobActive + teamData.isJobActive = !tempIsJobActive; + const savedTeam = await teamData.save(); + + !tempIsJobActive && await addNewAttendanceWeek(); + return { team: savedTeam, sanitizedAttendance: returnAttendanceData(teamData) } + }, + async recordAttendance( _: any, - { week, trainees, team, date, orgToken }: AttendanceInput, + { week, trainees, team, today, yesterday, orgToken }: AttendanceInput, context: Context ) { - const { teamData, phaseData } = await validateAttendance( + + const { teamData, phaseData, userId } = await validateAttendance( team, orgToken, trainees, context ) + + if (!today && !yesterday) { + throw new Error('Recording attendance is only allowed for today and the day before within work days.') + } + if (today && yesterday) { + throw new Error('Please select either today or yesterday, not both.') + } + let date = (today && new Date()).toString(); + + if (yesterday) { + const today = new Date(); + if (today.getDay() === 1) { + const lastFriday = new Date(today); + + lastFriday.setDate(today.getDate() - 3); + date = lastFriday.toString() + } else { + const previousDay = new Date(today); + previousDay.setDate(today.getDate() - 1); + date = previousDay.toString() + } + } + + // Check if the day is among work days + if (![1, 2, 3, 4, 5].includes(new Date(date).getDay())) { + throw new Error('Attendance can only be recorded on workdays.') + } + const attendance = await Attendance.findOne({ phase: phaseData.id, week, @@ -249,9 +496,9 @@ const attendanceResolver = { trainee: traineeData, status: [ { - ...trainees[i].status, - date: new Date(date!), - day: trainees[i].status.day.toLowerCase(), + date: new Date(date), + score: trainees[i].score, + day: new Date(date).toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase(), }, ], }) @@ -260,7 +507,7 @@ const attendanceResolver = { if (!attendants.length) { throw new Error( - "Invalid Ids for trainees or trainees doesn't belong to the team" + 'Invalid Ids for trainees or trainees doesn\'t belong to the team' ) } @@ -277,8 +524,8 @@ const attendanceResolver = { ], } - const savedAttendance = await Attendance.create(newAttendance) - return savedAttendance.teams[0] + await Attendance.create(newAttendance) + return returnAttendanceData(teamData) } // Adding new team to week attendance @@ -297,12 +544,17 @@ const attendanceResolver = { } ) .populate('teams.team') - .populate('teams.trainees.trainee', '-password') + .populate('teams.trainees.trainee', '-password'); + + attendants.forEach(attendant => { + pushNotification(attendant.trainee._id, `Your attendance for ${today ? 'today' : 'yesterday'} has been recorded, Kindly review your score.`, userId, 'attendance') + }); return savedAttendance?.teams[savedAttendance?.teams.length - 1] } let traineeStatusUpdated = false + const traineeIdsNotification = [] for (let i = 0; i < trainees.length; i++) { const traineeIndex = attendance.teams[ @@ -310,28 +562,27 @@ const attendanceResolver = { ].trainees.findIndex((traineeData) => (traineeData.trainee as UserInterface)._id.equals(trainees[i].trainee) ) - if (traineeIndex === -1) { + traineeStatusUpdated = true; const traineeData = await User.findOne( { _id: new ObjectId(trainees[i].trainee), team: teamData.id }, { password: 0 } ) if (traineeData) { - ;(attendance.teams[attendanceTeamIndex!].trainees as any[]).push({ + (attendance.teams[attendanceTeamIndex!].trainees as any[]).push({ trainee: traineeData, status: [ { - day: trainees[i].status.day.toLowerCase() as - | 'mon' - | 'tue' - | 'wed' - | 'thu' - | 'fri', - date: new Date(date!), - score: trainees[i].status.score as '0' | '1' | '2', + day: new Date(date).toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase(), + date: new Date(date), + score: trainees[i].score, }, ], }) + traineeIdsNotification.push({ + id: traineeData._id, + score: trainees[i].score + }); } } else { if ( @@ -350,7 +601,7 @@ const attendanceResolver = { const existingDay = attendance.teams[attendanceTeamIndex!].trainees[ traineeIndex - ].status.find((s) => s.day === trainees[i].status.day.toLowerCase()) + ].status.find((s) => s.day === new Date(date).toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase()) if ( ( @@ -363,15 +614,19 @@ const attendanceResolver = { attendance.teams[attendanceTeamIndex!].trainees[ traineeIndex ].status.push({ - day: trainees[i].status.day.toLowerCase() as + day: new Date(date).toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase() as | 'mon' | 'tue' | 'wed' | 'thu' | 'fri', date: new Date(date!), - score: trainees[i].status.score, - }) + score: trainees[i].score, + }); + traineeIdsNotification.push({ + id: (attendance.teams[attendanceTeamIndex!].trainees[traineeIndex].trainee as UserInterface)._id, + score: trainees[i].score + }); } } } @@ -386,27 +641,32 @@ const attendanceResolver = { ) } - const savedTeamAttendance = await ( - await attendance.save() - ).populate('teams.team') - return savedTeamAttendance.teams[attendanceTeamIndex!] + await attendance.save(); + traineeIdsNotification.forEach(trainee => { + pushNotification(trainee.id, `Your attendance for ${today ? 'today,' : 'yesterday,'} ${format(new Date(date), 'MMMM dd, yyyy')} has been recorded, Your score is ${trainee.score}.`, userId, 'attendance') + }) + return returnAttendanceData(teamData) }, async updateAttendance( _: any, - { week, trainees, team, orgToken, phase }: AttendanceInput, + { week, trainees, team, orgToken, day, phase }: AttendanceInput, context: Context ) { - const { teamData } = await validateAttendance( + + const { teamData, userId } = await validateAttendance( team, orgToken, trainees, - context + context, + true ) + const phaseData = await Phase.findById(phase) + if (!phaseData) { - throw new Error("Phase provided doesn't exist") + throw new Error('Phase provided doesn\'t exist') } const attendance = await Attendance.findOne({ phase: phaseData.id, @@ -417,6 +677,7 @@ const attendanceResolver = { (teamAttendanceData) => (teamAttendanceData.team as ObjectId).equals(teamData.id) ) + if (!attendance || teamToUpdateIndex === -1) { throw new GraphQLError('Invalid week or team', { extensions: { @@ -424,7 +685,9 @@ const attendanceResolver = { }, }) } - const teamAttendanceTrainees = attendance.teams[teamToUpdateIndex!] + const teamAttendanceTrainees = attendance.teams[teamToUpdateIndex!]; + const date = teamAttendanceTrainees.date ? format(new Date(getDateForDays(teamAttendanceTrainees.date.getTime().toString())[day].date), 'MMMM dd, yyyy') : day; + const traineeIdsNotification: { id: Types.ObjectId, score: number }[] = []; trainees.forEach((sentTrainee) => { let isDropped = false @@ -447,19 +710,28 @@ const attendanceResolver = { } ) } + if (traineeIndex !== -1 && !isDropped) { - const traineeToUpdateStatus = - teamAttendanceTrainees.trainees[traineeIndex].status + const traineeToUpdateStatus = teamAttendanceTrainees.trainees[traineeIndex].status; + traineeToUpdateStatus.forEach((status) => { if ( - status.day === trainees[traineeIndex].status.day.toLowerCase() + status.day === day.toLowerCase() ) { - status.score = trainees[traineeIndex].status.score + (status.score != sentTrainee.score) && traineeIdsNotification.push({ + id: (teamAttendanceTrainees.trainees[traineeIndex].trainee as UserInterface)._id, + score: sentTrainee.score + }); + status.score = sentTrainee.score; } }) } }) - await attendance.save() + + await attendance.save(); + traineeIdsNotification.forEach(trainee => { + pushNotification(trainee.id, `Your attendance record for ${date}, has been updated, Your new score is ${trainee.score}.`, userId, 'attendance') + }) return returnAttendanceData(teamData) }, @@ -468,31 +740,35 @@ const attendanceResolver = { { week, day, team }: { week: string; day: string; team: string }, context: Context ) { - ;(await checkUserLoggedIn(context))(['coordinator']) + (await checkUserLoggedIn(context))(['coordinator', 'ttl']) const teamData = await Team.findById(team) .populate('cohort') .populate('cohort.phase') if (!teamData) { - throw new Error("Team provided doesn't exist") + throw new Error('Team provided doesn\'t exist') } + const phase = teamData.phase || (teamData.cohort as CohortInterface).phase._id + const attendance = await Attendance.findOne({ - phase: (teamData?.cohort as CohortInterface).phase._id, + phase: phase, week, cohort: teamData.cohort, - }).populate('teams.trainees.trainee', '-password') + }).populate('teams.trainees.trainee', '-password'); + const attendanceTeamIndex = attendance?.teams.findIndex( (teamAttendanceData) => (teamAttendanceData.team as ObjectId).equals(teamData.id) ) if (!attendance || attendanceTeamIndex === -1) { - throw new Error("Can't find the Attendance for this day") + throw new Error('Can\'t find the Attendance for this day') } let removedAttendances = 0 + // attendance.teams[attendanceTeamIndex!].date attendance.teams[attendanceTeamIndex!].trainees.forEach((trainee) => { const statusIndex = trainee.status.findIndex( (s) => s.day === day.toLowerCase() @@ -508,7 +784,7 @@ const attendanceResolver = { return returnAttendanceData(teamData) } - throw new Error("Can't find the Attendance for this day") + throw new Error('Can\'t find the Attendance for this day') }, }, } diff --git a/src/resolvers/cohort.resolvers.ts b/src/resolvers/cohort.resolvers.ts index 83358dad..7e7c94b0 100644 --- a/src/resolvers/cohort.resolvers.ts +++ b/src/resolvers/cohort.resolvers.ts @@ -12,6 +12,7 @@ import { ProgramType } from './program.resolvers' import { OrganizationType } from './userResolver' import { pushNotification } from '../utils/notification/pushNotification' import { Types } from 'mongoose' +import { addNewAttendanceWeek } from '../utils/cron-jobs/team-jobs' export type CohortType = InstanceType @@ -100,8 +101,8 @@ const resolvers = { orgToken, } = args - // some validations - ;(await checkUserLoggedIn(context))([ + // some validations + ; (await checkUserLoggedIn(context))([ RoleOfUser.SUPER_ADMIN, RoleOfUser.ADMIN, RoleOfUser.MANAGER, @@ -147,7 +148,7 @@ const resolvers = { endDate && isAfter(new Date(startDate.toString()), new Date(endDate.toString())) ) { - throw new GraphQLError("End Date can't be before Start Date", { + throw new GraphQLError('End Date can\'t be before Start Date', { extensions: { code: 'VALIDATION_ERROR', }, @@ -180,7 +181,7 @@ const resolvers = { `You\'ve been assigned a new cohort "${name}"`, senderId ) - + addNewAttendanceWeek() return newCohort } catch (error) { const { message } = error as { message: any } @@ -291,7 +292,7 @@ const resolvers = { new Date(endDate) )) ) { - throw new GraphQLError("End Date can't be before Start Date", { + throw new GraphQLError('End Date can\'t be before Start Date', { extensions: { code: 'VALIDATION_ERROR', }, @@ -371,7 +372,8 @@ const resolvers = { notificationChanges.push('Name') } if (phaseName && cohort.phase.toString() !== phase.id.toString()) { - cohort.phase = phase.id + cohort.phase = phase.id; + addNewAttendanceWeek(); notificationChanges.push('Phase') } @@ -399,10 +401,8 @@ const resolvers = { if (notificationChanges.length) { pushNotification( coordinator.id, - `${ - role[0].toUpperCase() + role.slice(1) - } has made the following changes to "${ - cohort.name + `${role[0].toUpperCase() + role.slice(1) + } has made the following changes to "${cohort.name }": ${notificationChanges.join(', ')}`, senderId ) diff --git a/src/resolvers/coordinatorResolvers.ts b/src/resolvers/coordinatorResolvers.ts index a3da87a0..4b26f063 100644 --- a/src/resolvers/coordinatorResolvers.ts +++ b/src/resolvers/coordinatorResolvers.ts @@ -431,51 +431,51 @@ const manageStudentResolvers = { if (!user.team) { // add trainee to attendance - if (role === RoleOfUser.COORDINATOR) { - const attendanceRecords: any = Attendance.find({ - coordinatorId: userId, - }) - - const traineeArray = (await attendanceRecords).map( - (data: any) => data.trainees - ) - - let traineeEmailExists = false - for (const weekTrainees of traineeArray) { - for (const trainee of weekTrainees) { - if (trainee.traineeEmail === email) { - traineeEmailExists = true - break - } - } - } - if (!traineeEmailExists) { - // create new trainee - const newTrainee: Trainee = { - traineeId: user.id, - traineeEmail: email, - status: [], - } - - const attendanceLength: any = await Attendance.find({ - coordinatorId: userId, - }) - - if (attendanceLength.length > 0) { - for (const attendData of attendanceLength) { - attendData.trainees.push(newTrainee) - await attendData.save() - } - } else { - const newAttendRecord = new Attendance({ - week: 1, - coordinatorId: [userId], - trainees: [newTrainee], - }) - await newAttendRecord.save() - } - } - } + // if (role === RoleOfUser.COORDINATOR) { + // const attendanceRecords: any = Attendance.find({ + // coordinatorId: userId, + // }) + + // const traineeArray = (await attendanceRecords).map( + // (data: any) => data.trainees + // ) + + // let traineeEmailExists = false + // for (const weekTrainees of traineeArray) { + // for (const trainee of weekTrainees) { + // if (trainee.traineeEmail === email) { + // traineeEmailExists = true + // break + // } + // } + // } + // if (!traineeEmailExists) { + // // create new trainee + // const newTrainee: Trainee = { + // traineeId: user.id, + // traineeEmail: email, + // status: [], + // } + + // const attendanceLength: any = await Attendance.find({ + // coordinatorId: userId, + // }) + + // if (attendanceLength.length > 0) { + // for (const attendData of attendanceLength) { + // attendData.trainees.push(newTrainee) + // await attendData.save() + // } + // } else { + // const newAttendRecord = new Attendance({ + // week: 1, + // coordinatorId: [userId], + // trainees: [newTrainee], + // }) + // await newAttendRecord.save() + // } + // } + // } user.team = team.id user.cohort = team.cohort.id diff --git a/src/resolvers/team.resolvers.ts b/src/resolvers/team.resolvers.ts index 7e67e68d..d6ef7b82 100644 --- a/src/resolvers/team.resolvers.ts +++ b/src/resolvers/team.resolvers.ts @@ -14,6 +14,7 @@ import { Rating } from '../models/ratings' import { pushNotification } from '../utils/notification/pushNotification' import { Types } from 'mongoose' import { GraphQLError } from 'graphql' +import { addNewAttendanceWeek } from '../utils/cron-jobs/team-jobs' const resolvers = { Team: { @@ -48,6 +49,23 @@ const resolvers = { }, }, Query: { + getTTLTeams: async (_: any, { orgToken }: any, context: Context) => { + try { + const { userId } = (await checkUserLoggedIn(context))([RoleOfUser.TTL]) + const org = await checkLoggedInOrganization(orgToken) + + const teams = await Team.find({ organization: org, ttl: userId }).populate('phase') + return teams; + + } catch (error) { + const { message } = error as { message: any } + throw new GraphQLError(message.toString(), { + extensions: { + code: '500', + }, + }) + } + }, getAllTeams: async (_: any, { orgToken }: any, context: Context) => { try { // some validations @@ -156,7 +174,6 @@ const resolvers = { }, }) ).filter((item: any) => { - console.log(item) const org = (item.program as InstanceType) ?.organization @@ -271,8 +288,8 @@ const resolvers = { try { const { name, cohortName, orgToken, startingPhase, ttlEmail } = args - // some validations - ;(await checkUserLoggedIn(context))([RoleOfUser.SUPER_ADMIN, RoleOfUser.ADMIN, RoleOfUser.MANAGER]) + // some validations + ; (await checkUserLoggedIn(context))([RoleOfUser.SUPER_ADMIN, RoleOfUser.ADMIN, RoleOfUser.MANAGER]) const cohort = await Cohort.findOne({ name: cohortName }) const organ = await checkLoggedInOrganization(orgToken) @@ -335,6 +352,7 @@ const resolvers = { senderId ) + addNewAttendanceWeek(); return newTeam } catch (error: any) { const { message } = error as { message: any } @@ -346,7 +364,7 @@ const resolvers = { } }, deleteTeam: async (parent: any, args: any, context: Context) => { - ;(await checkUserLoggedIn(context))([RoleOfUser.ADMIN, RoleOfUser.MANAGER]) + ; (await checkUserLoggedIn(context))([RoleOfUser.ADMIN, RoleOfUser.MANAGER]) const findTeam = await Team.findById(args.id) if (!findTeam) throw new Error('The Team you want to delete does not exist') @@ -362,8 +380,6 @@ const resolvers = { await Team.findByIdAndDelete({ _id: args.id }) cohort ? (cohort.teams = cohort.teams - 1) : null cohort?.save() - cohort && - console.log('done-----------------', cohort.coordinator.toString()) const senderId = new Types.ObjectId(context.userId) cohort && @@ -591,6 +607,7 @@ const resolvers = { strictPopulate: false, }) + addNewAttendanceWeek(); return updatedteam }, }, diff --git a/src/resolvers/userResolver.ts b/src/resolvers/userResolver.ts index 39bbb1ef..11733557 100644 --- a/src/resolvers/userResolver.ts +++ b/src/resolvers/userResolver.ts @@ -34,6 +34,7 @@ import organizationRejectedTemplate from '../utils/templates/organizationRejecte import registrationRequest from '../utils/templates/registrationRequestTemplate' import { EmailPattern } from '../utils/validation.utils' import { Context } from './../context' +import { UserInputError } from 'apollo-server' const octokit = new Octokit({ auth: `${process.env.Org_Repo_Access}` }) const SECRET: string = process.env.SECRET ?? 'test_secret' @@ -49,7 +50,7 @@ enum Status { const resolvers: any = { Query: { async getOrganizations(_: any, __: any, context: Context) { - ;(await checkUserLoggedIn(context))([RoleOfUser.SUPER_ADMIN]) + ; (await checkUserLoggedIn(context))([RoleOfUser.SUPER_ADMIN]) return Organization.find() }, @@ -103,7 +104,7 @@ const resolvers: any = { { organisation, username }: any, context: Context ) { - ;(await checkUserLoggedIn(context))([ + ; (await checkUserLoggedIn(context))([ RoleOfUser.ADMIN, RoleOfUser.COORDINATOR, 'trainee', @@ -115,7 +116,7 @@ const resolvers: any = { name: organisation, }) if (!organisationExists) - throw new Error("This Organization doesn't exist") + throw new Error('This Organization doesn\'t exist') organisation = organisationExists.gitHubOrganisation @@ -196,6 +197,12 @@ const resolvers: any = { } }, }, + Login: { + user: async (parent: any) => { + const user = await User.findById(parent.user.id) + return user; + } + }, Mutation: { async createUser( _: any, @@ -328,8 +335,7 @@ const resolvers: any = { }) } else if (user?.status?.status !== 'active') { throw new GraphQLError( - `Your account have been ${ - user?.status?.status ?? user?.status + `Your account have been ${user?.status?.status ?? user?.status }, please contact your organization admin for assistance`, { extensions: { @@ -586,9 +592,9 @@ const resolvers: any = { ] const org = await checkLoggedInOrganization(orgToken) const roleExists = allRoles.includes(name) - if (!roleExists) throw new Error("This role doesn't exist") + if (!roleExists) throw new Error('This role doesn\'t exist') const userExists = await User.findById(id) - if (!userExists) throw new Error("User doesn't exist") + if (!userExists) throw new Error('User doesn\'t exist') const getAllUsers = await User.find({ role: RoleOfUser.ADMIN, @@ -814,7 +820,7 @@ const resolvers: any = { context: Context ) { // check if requester is super admin - ;(await checkUserLoggedIn(context))([RoleOfUser.SUPER_ADMIN]) + ; (await checkUserLoggedIn(context))([RoleOfUser.SUPER_ADMIN]) const orgExists = await Organization.findOne({ name: name }) if (action == 'approve') { if (!orgExists) { @@ -884,7 +890,7 @@ const resolvers: any = { context: Context ) { // the below commented line help to know if the user is an superAdmin to perform an action of creating an organization - ;(await checkUserLoggedIn(context))([RoleOfUser.SUPER_ADMIN]) + ; (await checkUserLoggedIn(context))([RoleOfUser.SUPER_ADMIN]) if (action == 'new') { const orgExists = await Organization.findOne({ name: name }) if (orgExists) { @@ -949,7 +955,7 @@ const resolvers: any = { { name, gitHubOrganisation }: any, context: Context ) { - ;(await checkUserLoggedIn(context))([ + ; (await checkUserLoggedIn(context))([ RoleOfUser.ADMIN, RoleOfUser.SUPER_ADMIN, ]) @@ -1042,7 +1048,7 @@ const resolvers: any = { }, async deleteOrganization(_: any, { id }: any, context: Context) { - ;(await checkUserLoggedIn(context))([ + ; (await checkUserLoggedIn(context))([ RoleOfUser.ADMIN, RoleOfUser.SUPER_ADMIN, ]) @@ -1050,7 +1056,7 @@ const resolvers: any = { const organizationExists = await Organization.findOne({ _id: id }) if (!organizationExists) - throw new Error("This Organization doesn't exist") + throw new Error('This Organization doesn\'t exist') await Cohort.deleteMany({ organization: id }) await Team.deleteMany({ organization: id }) await Phase.deleteMany({ organization: id }) @@ -1128,7 +1134,7 @@ const resolvers: any = { if (password === confirmPassword) { const user: any = await User.findOne({ email }) if (!user) { - throw new Error("User doesn't exist! ") + throw new Error('User doesn\'t exist! ') } user.password = password await user.save() diff --git a/src/schema/index.ts b/src/schema/index.ts index 104941a3..c782403a 100644 --- a/src/schema/index.ts +++ b/src/schema/index.ts @@ -57,6 +57,7 @@ const Schema = gql` members: [User] startingPhase: DateTime active: Boolean + isJobActive: Boolean organization: Organization phase:Phase manager:User @@ -312,7 +313,8 @@ const Schema = gql` fetchRatingByCohort(CohortName: String): [Rating] fetchCohortsCoordinator(cohortName: ID!): [Cohort] verifyResetPasswordToken(token: String!): String - getAllTeams(orgToken: String): [Team!] + getTTLTeams(orgToken: String): [Team!]! + getAllTeams(orgToken: String): [Team!]! getAllTeamInCohort(orgToken: String, cohort: String): [Team!] gitHubActivity(organisation: String!, username: String!): GitHubActivity! } @@ -633,8 +635,52 @@ const Schema = gql` attendancePerc: String! } + type AttendanceDatesData { + date: String! + isValid: Boolean! + } + type AttendanceDates { + mon: AttendanceDatesData! + tue: AttendanceDatesData! + wed: AttendanceDatesData! + thu: AttendanceDatesData! + fri: AttendanceDatesData! + } + type TraineeAttendanceData { + trainee: User! + score: Int! + } + type AttendanceDays { + mon: [TraineeAttendanceData]! + tue: [TraineeAttendanceData]! + wed: [TraineeAttendanceData]! + thu: [TraineeAttendanceData]! + fri: [TraineeAttendanceData]! + } + type AttendanceWeeks { + phase: Phase! + weeks: [Int!] + } + type FilteredAttendance { + week: Int! + phase: Phase! + dates: AttendanceDates! + days: AttendanceDays! + } + type SanitizedAttendance { + today: String! + yesterday: String! + attendanceWeeks: [AttendanceWeeks!]! + attendance: [FilteredAttendance!]! + } + + type PauseAndResumeTeamAttendance { + team: Team! + sanitizedAttendance: SanitizedAttendance! + } + type Query { - getTeamAttendance(orgToken: String, team: String!): [Attendance] + getTeamAttendance(orgToken: String, team: String!): SanitizedAttendance getTraineeAttendanceByID(traineeEmail: String!): [weeklyAttendance] getAttendanceStats(orgToken: String!): [AttendanceStats] } @@ -642,30 +688,36 @@ const Schema = gql` recordAttendance( week: Int! team: String! - date: String! + today: Boolean! + yesterday: Boolean! trainees: [TraineeInput!]! orgToken: String! - ): AttendanceTeam + ): SanitizedAttendance + + pauseAndResumeTeamAttendance( + team: String! + orgToken: String + ): PauseAndResumeTeamAttendance updateAttendance( week: Int! + day: String! team: String! phase: String! trainees: [TraineeInput!]! orgToken: String! - ): [Attendance] + ): SanitizedAttendance - deleteAttendance(week: String!, team: String!, day: String!): [Attendance] - } - - input StatusInput { - day: String! - score: String! + deleteAttendance( + week: Int!, + team: String!, + day: String! + ): SanitizedAttendance } input TraineeInput { trainee: ID! - status: StatusInput! + score: Int! } type Session { id: String diff --git a/src/utils/cron-jobs/team-jobs.ts b/src/utils/cron-jobs/team-jobs.ts new file mode 100644 index 00000000..489a9f39 --- /dev/null +++ b/src/utils/cron-jobs/team-jobs.ts @@ -0,0 +1,124 @@ +import cron from 'node-cron'; +import Team from '../../models/team.model'; +import { Attendance } from '../../models/attendance.model'; +import Cohort, { CohortInterface } from '../../models/cohort.model'; +import { isSameWeek } from 'date-fns'; +import mongoose from 'mongoose'; + +export const addNewAttendanceWeek = async () => { + try { + const completedTeamsId: string[] = []; + const cohorts = await Cohort.find({ active: true }); + for (const cohort of cohorts) { + const attendances = await Attendance.find({ cohort: cohort._id, phase: cohort.phase }); + if (!attendances.length) { + const teams = await Team.find({ cohort, active: true, isJobActive: true }); + await Attendance.create({ + week: 1, + phase: cohort.phase, + cohort: cohort, + teams: teams.map(team => { + const phase = (team.phase as mongoose.Types.ObjectId); + if (phase && phase.equals(cohort.phase.toString())) { + completedTeamsId.push(team._id.toString()); + return { team, trainees: [] }; + } + if (!phase) { + completedTeamsId.push(team._id.toString()); + return { team, trainees: [] }; + } + }).filter(team => team) + }) + } + } + + const teams = await Team.find({ active: true, isJobActive: true }).populate('cohort'); + + for (const team of teams) { + const phase = team.phase || (team.cohort as CohortInterface).phase + const attendances = await Attendance.find({ cohort: (team.cohort as CohortInterface)._id, phase: phase }); + + let lastWeek = 0; + let attendanceIndex: number | undefined; + let teamAttendanceDate; + for (let index = 0; index < attendances.length; index++) { + const attendance = attendances[index]; + if (attendance.week > lastWeek) { + for (const teamAttendance of attendance.teams) { + if (team._id.equals(teamAttendance.team)) { + lastWeek = attendance.week + attendanceIndex = index; + teamAttendanceDate = teamAttendance.date; + } + } + } + } + + if (lastWeek && lastWeek < 43 && teamAttendanceDate && (attendanceIndex! >= 0)) { + + const isInSameWeek = isSameWeek( + new Date(), + new Date(teamAttendanceDate), + { + weekStartsOn: 1 + } + ); + if (!isInSameWeek && (new Date().getTime() > new Date(teamAttendanceDate).getTime())) { + const attendanceExist = await Attendance.findOne({ week: (lastWeek + 1), phase: phase, cohort: (team.cohort as CohortInterface)._id }) + if (attendanceExist) { + completedTeamsId.push(team._id.toString()); + attendanceExist.teams.push({ + team: team._id, + trainees: [] + }) + await attendanceExist.save() + } else { + const tempTeams = await Team.find({ active: true, isJobActive: true, cohort: (team.cohort as CohortInterface)._id }).populate('cohort') + await Attendance.create({ + week: (lastWeek + 1), + phase, + cohort: (team.cohort as CohortInterface)._id, + teams: tempTeams.map(team => { + if (!completedTeamsId.includes(team._id.toString()) && team.phase && (team.phase as mongoose.Types.ObjectId).equals(phase.toString())) { + return { team, trainees: [] }; + } + if (!completedTeamsId.includes(team._id.toString())) { + return { team, trainees: [] }; + } + }).filter(team => team) + }) + } + } + } + if (attendances.length && attendanceIndex === undefined) { + const tempAttendance = await Attendance.findOne({ week: (lastWeek + 1), phase, cohort: (team.cohort as CohortInterface)._id }).populate('cohort') + tempAttendance?.teams.push({ + team: team._id, + trainees: [] + }) + await tempAttendance?.save(); + } + if (!attendances.length) { + const tempTeams = await Team.find({ active: true, isJobActive: true, cohort: (team.cohort as CohortInterface)._id }).populate('cohort') + await Attendance.create({ + week: (lastWeek + 1), + phase, + cohort: (team.cohort as CohortInterface)._id, + teams: tempTeams.map(team => { + const isPhaseTrue = (team.phase && (team.phase as mongoose.Types.ObjectId).equals(phase.toString())) || ((team.cohort as CohortInterface).phase as unknown as mongoose.Types.ObjectId).equals(phase.toString()) + if (!completedTeamsId.includes(team._id.toString()) && isPhaseTrue) { + return { team, trainees: [] }; + } + }).filter(team => team) + }) + } + } + } catch (error) { + console.error('Error in scheduled job:', error); + } +} + +// Schedule a job to add a new week on monday +cron.schedule('0 0 * * 1', async () => { + addNewAttendanceWeek(); +}); \ No newline at end of file diff --git a/src/utils/getDateForDays.ts b/src/utils/getDateForDays.ts new file mode 100644 index 00000000..08b0500c --- /dev/null +++ b/src/utils/getDateForDays.ts @@ -0,0 +1,77 @@ +import { WeekdaysInterface } from '../resolvers/attendance.resolvers'; + +const handleAttendanceDay = (dayDate: string) => { + const today = new Date(); + const input = new Date(dayDate); + + today.setHours(0, 0, 0, 0); + input.setHours(0, 0, 0, 0); + + const previousDay = new Date(today); + previousDay.setDate(today.getDate() - 1); + + if (input.getTime() === today.getTime()) { + return true; + } + + if (input.getTime() === previousDay.getTime()) { + return true; + } + + // Check if today is Monday and selectedDayDate is last Friday + if (today.getDay() === 1 && input.getDay() === 5) { + const lastFriday = new Date(today); + lastFriday.setDate(today.getDate() - 3); + + return input.getTime() === lastFriday.getTime(); + } + + return false; +}; + +const days: ('mon' | 'tue' | 'wed' | 'thu' | 'fri')[] = ['mon', 'tue', 'wed', 'thu', 'fri']; + +export const getDateForDays = (inputDate: string) => { + try { + const date = new Date(Number(inputDate)); + let dayOfWeek = date.getDay(); + + if (date.getUTCHours() >= 22) { + date.setUTCDate(date.getUTCDate() + 1); + date.setUTCHours(0, 0, 0, 0); // Set the time to midnight of the next day + } + + if (dayOfWeek === 0) { + dayOfWeek = 7 + } + const dateObj: WeekdaysInterface = { + mon: { date: '', isValid: false }, + tue: { date: '', isValid: false }, + wed: { date: '', isValid: false }, + thu: { date: '', isValid: false }, + fri: { date: '', isValid: false } + }; + + for (let i = 1; i <= 5; i++) { + const daysToAdd = i - dayOfWeek; + const weekdayDate = new Date(date); + weekdayDate.setDate(date.getDate() + daysToAdd); + + dateObj[days[i - 1]].date = weekdayDate.toISOString().split('T')[0]; + dateObj[days[i - 1]].isValid = handleAttendanceDay(weekdayDate.toISOString()) + } + + return dateObj; + } catch (error) { + return { + mon: { date: '', isValid: false }, + tue: { date: '', isValid: false }, + wed: { date: '', isValid: false }, + thu: { date: '', isValid: false }, + fri: { date: '', isValid: false } + } + } + +}; + +