feat: add radar chart

This commit is contained in:
Nathan Lamy 2025-08-20 18:18:03 +02:00
parent fb00495840
commit 7093b344ed
3 changed files with 54 additions and 24 deletions

View file

@ -3,7 +3,7 @@ import { inject } from '@adonisjs/core'
import type { HttpContext } from '@adonisjs/core/http' import type { HttpContext } from '@adonisjs/core/http'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
const PERIODS = ['MONTH', 'SEMESTER', 'YEAR'] const PERIODS = ['MONTH', 'TRIMESTER', 'YEAR']
@inject() @inject()
export default class GradesController { export default class GradesController {
@ -37,5 +37,22 @@ export default class GradesController {
return this.gradeService.getPeriodGrade(userId, startDate, months) return this.gradeService.getPeriodGrade(userId, startDate, months)
} }
// TODO: Radar chart for subjects // GET /averages
async averages({ auth, request, response }: HttpContext) {
const period = request.input('period', PERIODS[0]).toUpperCase()
if (!PERIODS.includes(period)) {
return response.badRequest({
message: `Invalid period. Allowed values are: ${PERIODS.join(', ')}`,
})
}
const userId = auth.user!.id
if (period === PERIODS[0]) {
const startDate = DateTime.now().minus({ month: 1 })
return this.gradeService.getSubjectsAverages(userId, startDate, 1)
}
const months = period === PERIODS[1] ? 3 : 12
const startDate = DateTime.now().minus({ months })
return this.gradeService.getSubjectsAverages(userId, startDate, months)
}
} }

View file

@ -3,14 +3,16 @@ import { DateTime } from 'luxon'
export class GradeService { export class GradeService {
private calculateAverage(colles: Colle[]) { private calculateAverage(colles: Colle[]) {
const total = colles.reduce((sum, colle) => sum + colle.grade, 0) const total = colles
return parseFloat((total / colles.length).toFixed(2)) .map((colle) => parseFloat(colle.grade?.toString()))
.filter(Boolean)
.reduce((sum, grade) => sum + grade, 0)
return (total / colles.length).toFixed(2)
} }
private calculateSubjectAverage(colles: Colle[], subject: string) { private calculateSubjectAverage(colles: Colle[], subject: string) {
const subjectColles = colles.filter((colle) => colle.subject.name === subject) const subjectColles = colles.filter((colle) => colle.subject.name === subject && colle.grade)
const total = subjectColles.reduce((sum, colle) => sum + colle.grade, 0) return this.calculateAverage(subjectColles)
return parseFloat((total / subjectColles.length).toFixed(2))
} }
private getSubjects(colles: Colle[]) { private getSubjects(colles: Colle[]) {
@ -21,7 +23,22 @@ export class GradeService {
return colles.filter((colle) => colle.date >= startDate && colle.date < endDate) return colles.filter((colle) => colle.date >= startDate && colle.date < endDate)
} }
private getColles(userId: number, startDate: DateTime, months: number = 0) { public async getSubjectsAverages(userId: number, startDate: DateTime, months: number = 0) {
const colles = await this.getColles(userId, startDate, months)
const subjects = this.getSubjects(colles)
const subjectAverages: SubjectPerformance[] = []
for (const subject of subjects) {
const average = this.calculateSubjectAverage(colles, subject)
subjectAverages.push({ subject, average })
}
const globalAverage = this.calculateAverage(colles)
return {
globalAverage,
subjectAverages,
}
}
public getColles(userId: number, startDate: DateTime, months: number = 0) {
const endDate = startDate.plus({ months }) const endDate = startDate.plus({ months })
return Colle.query() return Colle.query()
.where('studentId', userId) .where('studentId', userId)
@ -43,7 +60,7 @@ export class GradeService {
const colles = await this.getColles(userId, startDate, 1) const colles = await this.getColles(userId, startDate, 1)
const subjects = this.getSubjects(colles) const subjects = this.getSubjects(colles)
const results: PeriodResult[] = [] const results: any = []
let periodStartDate = startDate let periodStartDate = startDate
for (let week = 1; week <= 4; week++) { for (let week = 1; week <= 4; week++) {
@ -77,14 +94,14 @@ export class GradeService {
const colles = await this.getColles(userId, startDate, months) const colles = await this.getColles(userId, startDate, months)
const subjects = this.getSubjects(colles) const subjects = this.getSubjects(colles)
const results: PeriodResult[] = [] const results: any = []
let periodStartDate = startDate let periodStartDate = startDate
let index = 1 let index = 1
while (periodStartDate < startDate.plus({ months })) { while (periodStartDate < startDate.plus({ months })) {
const periodEndDate = periodStartDate.endOf('month') const periodEndDate = periodStartDate.endOf('month')
const periodColles = this.getPeriodColles(colles, periodStartDate, periodEndDate) const periodColles = this.getPeriodColles(colles, startDate, periodEndDate)
const periodAverage = this.calculateAverage(periodColles) const periodAverage = this.calculateAverage(periodColles)
const subjectAverages = await this.getSubjectAverages( const subjectAverages = await this.getSubjectAverages(
@ -121,14 +138,17 @@ export class GradeService {
subjects: string[], subjects: string[],
periodColles: Colle[], periodColles: Colle[],
allColles: Colle[], allColles: Colle[],
previousResults: PeriodResult[], previousResults: Record<string, number>[],
unitIndex: number, unitIndex: number,
userId: number, userId: number,
periodStart: DateTime periodStart: DateTime
) { ) {
const results = await Promise.all( const results = await Promise.all(
subjects.map(async (subject) => { subjects.map(async (subject) => {
if (periodColles.length > 0) { const subjectColles = periodColles.filter(
(colle) => colle.subject.name === subject && colle.grade
)
if (subjectColles.length > 0) {
return { return {
subject, subject,
average: this.calculateSubjectAverage(periodColles, subject), average: this.calculateSubjectAverage(periodColles, subject),
@ -137,11 +157,9 @@ export class GradeService {
// Try to use the previous unit's average // Try to use the previous unit's average
if (unitIndex > 1) { if (unitIndex > 1) {
const prev = previousResults[unitIndex - 2].subjectAverages?.find( const prev = previousResults[unitIndex - 2]?.[subject]
(s) => s.subject === subject
)
if (prev) { if (prev) {
return { subject, average: prev.average } return { subject, average: prev }
} }
} }
@ -163,11 +181,5 @@ export class GradeService {
interface SubjectPerformance { interface SubjectPerformance {
subject: string subject: string
average: number average: string
}
interface PeriodResult {
period: string
average: number
subjectAverages: SubjectPerformance[]
} }

View file

@ -62,6 +62,7 @@ router
const GradesController = () => import('#controllers/grades_controller') const GradesController = () => import('#controllers/grades_controller')
router.get('/grades', [GradesController, 'index']).use(middleware.auth()) router.get('/grades', [GradesController, 'index']).use(middleware.auth())
router.get('/averages', [GradesController, 'averages']).use(middleware.auth())
const InternalsController = () => import('#controllers/internals_controller') const InternalsController = () => import('#controllers/internals_controller')
router router