DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on • Updated on

Detail implementation of Reports and Analytics

Below is an implementation for generating various reports for academic performance, attendance, and finances using Next.js, NestJS, and GraphQL.

Backend (NestJS)

1. Entities

We will use the entities defined previously for courses, assignments, submissions, fees, payments, and attendance.

2. Services

Report Service:

// report.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Course } from './course.entity';
import { Assignment } from './assignment.entity';
import { Submission } from './submission.entity';
import { Fee } from './fee.entity';
import { Payment } from './payment.entity';
import { Attendance } from './attendance.entity';

@Injectable()
export class ReportService {
  constructor(
    @InjectRepository(Course)
    private courseRepository: Repository<Course>,
    @InjectRepository(Assignment)
    private assignmentRepository: Repository<Assignment>,
    @InjectRepository(Submission)
    private submissionRepository: Repository<Submission>,
    @InjectRepository(Fee)
    private feeRepository: Repository<Fee>,
    @InjectRepository(Payment)
    private paymentRepository: Repository<Payment>,
    @InjectRepository(Attendance)
    private attendanceRepository: Repository<Attendance>,
  ) {}

  async getAcademicPerformanceReport(courseId: number) {
    const course = await this.courseRepository.findOne(courseId, { relations: ['assignments', 'assignments.submissions'] });
    const report = course.assignments.map(assignment => ({
      assignmentTitle: assignment.title,
      submissions: assignment.submissions.length,
      averageScore: assignment.submissions.reduce((acc, sub) => acc + sub.score, 0) / assignment.submissions.length,
    }));
    return report;
  }

  async getAttendanceReport() {
    const attendances = await this.attendanceRepository.find({ relations: ['student', 'class'] });
    const report = attendances.map(attendance => ({
      student: attendance.student.username,
      class: attendance.class.name,
      date: attendance.date,
      status: attendance.status,
    }));
    return report;
  }

  async getFinancialReport() {
    const fees = await this.feeRepository.find({ relations: ['user'] });
    const payments = await this.paymentRepository.find({ relations: ['fee', 'fee.user'] });

    const totalFees = fees.reduce((acc, fee) => acc + fee.amount, 0);
    const totalPayments = payments.reduce((acc, payment) => acc + payment.amount, 0);

    return {
      totalFees,
      totalPayments,
      outstandingAmount: totalFees - totalPayments,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Resolvers

Report Resolver:

// report.resolver.ts
import { Resolver, Query, Args } from '@nestjs/graphql';
import { ReportService } from './report.service';

@Resolver()
export class ReportResolver {
  constructor(private reportService: ReportService) {}

  @Query(() => [Object])
  async academicPerformanceReport(@Args('courseId') courseId: number) {
    return this.reportService.getAcademicPerformanceReport(courseId);
  }

  @Query(() => [Object])
  async attendanceReport() {
    return this.reportService.getAttendanceReport();
  }

  @Query(() => Object)
  async financialReport() {
    return this.reportService.getFinancialReport();
  }
}
Enter fullscreen mode Exit fullscreen mode

Frontend (Next.js)

1. Apollo Client Setup

// apollo-client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:3000/graphql',
  cache: new InMemoryCache(),
});

export default client;
Enter fullscreen mode Exit fullscreen mode

2. Academic Performance Report Page

// pages/reports/academic-performance.js
import { useQuery, gql } from '@apollo/client';
import { useState } from 'react';

const GET_ACADEMIC_PERFORMANCE_REPORT = gql`
  query GetAcademicPerformanceReport($courseId: Int!) {
    academicPerformanceReport(courseId: $courseId) {
      assignmentTitle
      submissions
      averageScore
    }
  }
`;

export default function AcademicPerformanceReport() {
  const [courseId, setCourseId] = useState('');
  const { loading, error, data, refetch } = useQuery(GET_ACADEMIC_PERFORMANCE_REPORT, {
    variables: { courseId: parseInt(courseId) },
    skip: !courseId,
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    refetch();
  };

  return (
    <div>
      <h1>Academic Performance Report</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          placeholder="Course ID"
          value={courseId}
          onChange={(e) => setCourseId(e.target.value)}
        />
        <button type="submit">Generate Report</button>
      </form>
      {loading && <p>Loading...</p>}
      {error && <p>Error: {error.message}</p>}
      {data && (
        <ul>
          {data.academicPerformanceReport.map((report, index) => (
            <li key={index}>
              <h2>{report.assignmentTitle}</h2>
              <p>Submissions: {report.submissions}</p>
              <p>Average Score: {report.averageScore}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Attendance Report Page

// pages/reports/attendance.js
import { useQuery, gql } from '@apollo/client';

const GET_ATTENDANCE_REPORT = gql`
  query GetAttendanceReport {
    attendanceReport {
      student
      class
      date
      status
    }
  }
`;

export default function AttendanceReport() {
  const { loading, error, data } = useQuery(GET_ATTENDANCE_REPORT);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Attendance Report</h1>
      <ul>
        {data.attendanceReport.map((report, index) => (
          <li key={index}>
            <p>Student: {report.student}</p>
            <p>Class: {report.class}</p>
            <p>Date: {report.date}</p>
            <p>Status: {report.status}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Financial Report Page

// pages/reports/financial.js
import { useQuery, gql } from '@apollo/client';

const GET_FINANCIAL_REPORT = gql`
  query GetFinancialReport {
    financialReport {
      totalFees
      totalPayments
      outstandingAmount
    }
  }
`;

export default function FinancialReport() {
  const { loading, error, data } = useQuery(GET_FINANCIAL_REPORT);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Financial Report</h1>
      <p>Total Fees: {data.financialReport.totalFees}</p>
      <p>Total Payments: {data.financialReport.totalPayments}</p>
      <p>Outstanding Amount: {data.financialReport.outstandingAmount}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Schema

Define your GraphQL schema to match the resolver functions:

type AcademicPerformanceReport {
  assignmentTitle: String!
  submissions: Int!
  averageScore: Float!
}

type AttendanceReport {
  student: String!
  class: String!
  date: String!
  status: String!
}

type FinancialReport {
  totalFees: Float!
  totalPayments: Float!
  outstandingAmount: Float!
}

type Query {
  academicPerformanceReport(courseId: Int!): [AcademicPerformanceReport!]!
  attendanceReport: [AttendanceReport!]!
  financialReport: FinancialReport!
}
Enter fullscreen mode Exit fullscreen mode

This setup covers the backend and frontend code for generating various reports for academic performance, attendance, and finances. You can expand on this by adding more details, such as charts and graphs for better visualization, and additional report types as needed.

To add graphs for better visualization and additional report types, we'll use a charting library such as chart.js on the frontend. Below, I'll show you how to integrate chart.js to display the data in a more visual and interactive way.

Backend (NestJS)

The backend setup remains the same as previously defined for generating various reports.

Frontend (Next.js)

1. Install Chart.js and react-chartjs-2

First, install chart.js and react-chartjs-2:

npm install chart.js react-chartjs-2
Enter fullscreen mode Exit fullscreen mode

2. Update Academic Performance Report Page

pages/reports/academic-performance.js

import { useQuery, gql } from '@apollo/client';
import { useState } from 'react';
import { Bar } from 'react-chartjs-2';
import 'chart.js/auto';

const GET_ACADEMIC_PERFORMANCE_REPORT = gql`
  query GetAcademicPerformanceReport($courseId: Int!) {
    academicPerformanceReport(courseId: $courseId) {
      assignmentTitle
      submissions
      averageScore
    }
  }
`;

export default function AcademicPerformanceReport() {
  const [courseId, setCourseId] = useState('');
  const { loading, error, data, refetch } = useQuery(GET_ACADEMIC_PERFORMANCE_REPORT, {
    variables: { courseId: parseInt(courseId) },
    skip: !courseId,
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    refetch();
  };

  const chartData = {
    labels: data?.academicPerformanceReport.map((report) => report.assignmentTitle) || [],
    datasets: [
      {
        label: 'Average Score',
        data: data?.academicPerformanceReport.map((report) => report.averageScore) || [],
        backgroundColor: 'rgba(75, 192, 192, 0.6)',
      },
    ],
  };

  return (
    <div>
      <h1>Academic Performance Report</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          placeholder="Course ID"
          value={courseId}
          onChange={(e) => setCourseId(e.target.value)}
        />
        <button type="submit">Generate Report</button>
      </form>
      {loading && <p>Loading...</p>}
      {error && <p>Error: {error.message}</p>}
      {data && (
        <div>
          <Bar data={chartData} />
          <ul>
            {data.academicPerformanceReport.map((report, index) => (
              <li key={index}>
                <h2>{report.assignmentTitle}</h2>
                <p>Submissions: {report.submissions}</p>
                <p>Average Score: {report.averageScore}</p>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Update Attendance Report Page

pages/reports/attendance.js

import { useQuery, gql } from '@apollo/client';
import { Bar } from 'react-chartjs-2';
import 'chart.js/auto';

const GET_ATTENDANCE_REPORT = gql`
  query GetAttendanceReport {
    attendanceReport {
      student
      class
      date
      status
    }
  }
`;

export default function AttendanceReport() {
  const { loading, error, data } = useQuery(GET_ATTENDANCE_REPORT);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  const presentData = data.attendanceReport.filter((report) => report.status === 'Present');
  const absentData = data.attendanceReport.filter((report) => report.status === 'Absent');

  const chartData = {
    labels: [...new Set(data.attendanceReport.map((report) => report.date))],
    datasets: [
      {
        label: 'Present',
        data: presentData.map((report) => report.status === 'Present' ? 1 : 0),
        backgroundColor: 'rgba(75, 192, 192, 0.6)',
      },
      {
        label: 'Absent',
        data: absentData.map((report) => report.status === 'Absent' ? 1 : 0),
        backgroundColor: 'rgba(255, 99, 132, 0.6)',
      },
    ],
  };

  return (
    <div>
      <h1>Attendance Report</h1>
      <Bar data={chartData} />
      <ul>
        {data.attendanceReport.map((report, index) => (
          <li key={index}>
            <p>Student: {report.student}</p>
            <p>Class: {report.class}</p>
            <p>Date: {report.date}</p>
            <p>Status: {report.status}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Update Financial Report Page

pages/reports/financial.js

import { useQuery, gql } from '@apollo/client';
import { Pie } from 'react-chartjs-2';
import 'chart.js/auto';

const GET_FINANCIAL_REPORT = gql`
  query GetFinancialReport {
    financialReport {
      totalFees
      totalPayments
      outstandingAmount
    }
  }
`;

export default function FinancialReport() {
  const { loading, error, data } = useQuery(GET_FINANCIAL_REPORT);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  const chartData = {
    labels: ['Total Fees', 'Total Payments', 'Outstanding Amount'],
    datasets: [
      {
        label: 'Financial Data',
        data: [
          data.financialReport.totalFees,
          data.financialReport.totalPayments,
          data.financialReport.outstandingAmount,
        ],
        backgroundColor: ['rgba(75, 192, 192, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(255, 206, 86, 0.6)'],
      },
    ],
  };

  return (
    <div>
      <h1>Financial Report</h1>
      <Pie data={chartData} />
      <p>Total Fees: {data.financialReport.totalFees}</p>
      <p>Total Payments: {data.financialReport.totalPayments}</p>
      <p>Outstanding Amount: {data.financialReport.outstandingAmount}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Additional Report Types

You can add more types of reports, such as:

  1. Student Performance Report: Aggregates a student's grades across different courses.
  2. Monthly Attendance Summary: Summarizes attendance for each student across a month.
  3. Payment History Report: Lists all payments made by students over a period.

Below is an example of how you can add a Student Performance Report:

Backend (NestJS)

1. Service

Report Service:

// report.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Submission } from './submission.entity';
import { User } from './user.entity';

@Injectable()
export class ReportService {
  constructor(
    @InjectRepository(Submission)
    private submissionRepository: Repository<Submission>,
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  async getStudentPerformanceReport(studentId: number) {
    const student = await this.userRepository.findOne(studentId, { relations: ['submissions', 'submissions.assignment'] });
    const report = student.submissions.map(submission => ({
      assignmentTitle: submission.assignment.title,
      grade: submission.grade,
    }));
    return report;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Resolver

Report Resolver:

// report.resolver.ts
import { Resolver, Query, Args } from '@nestjs/graphql';
import { ReportService } from './report.service';

@Resolver()
export class ReportResolver {
  constructor(private reportService: ReportService) {}

  @Query(() => [Object])
  async studentPerformanceReport(@Args('studentId') studentId: number) {
    return this.reportService.getStudentPerformanceReport(studentId);
  }
}
Enter fullscreen mode Exit fullscreen mode

Frontend (Next.js)

1. Student Performance Report Page

pages/reports/student-performance.js

import { useQuery, gql } from '@apollo/client';
import { useState } from 'react';
import { Line } from 'react-chartjs-2';
import 'chart.js/auto';

const GET_STUDENT_PERFORMANCE_REPORT = gql`
  query GetStudentPerformanceReport($studentId: Int!) {
    studentPerformanceReport(studentId: $studentId) {
      assignmentTitle
      grade
    }
  }
`;

export default function StudentPerformanceReport() {
  const [studentId, setStudentId] = useState('');
  const { loading, error, data, refetch } = useQuery(GET_STUDENT_PERFORMANCE_REPORT, {
    variables: { studentId: parseInt(studentId) },
    skip: !studentId,
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    refetch();
  };

  const chartData = {
    labels: data?.studentPerformanceReport.map((report) => report.assignmentTitle) || [],
    datasets: [
      {
        label: 'Grades',
        data: data?.studentPerformanceReport.map((report) => report.grade) || [],
        background

Color: 'rgba(75, 192, 192, 0.6)',
        borderColor: 'rgba(75, 192, 192, 1)',
        fill: false,
      },
    ],
  };

  return (
    <div>
      <h1>Student Performance Report</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          placeholder="Student ID"
          value={studentId}
          onChange={(e) => setStudentId(e.target.value)}
        />
        <button type="submit">Generate Report</button>
      </form>
      {loading && <p>Loading...</p>}
      {error && <p>Error: {error.message}</p>}
      {data && (
        <div>
          <Line data={chartData} />
          <ul>
            {data.studentPerformanceReport.map((report, index) => (
              <li key={index}>
                <h2>{report.assignmentTitle}</h2>
                <p>Grade: {report.grade}</p>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Schema

Update your GraphQL schema to include the new StudentPerformanceReport type and query:

type AcademicPerformanceReport {
  assignmentTitle: String!
  submissions: Int!
  averageScore: Float!
}

type AttendanceReport {
  student: String!
  class: String!
  date: String!
  status: String!
}

type FinancialReport {
  totalFees: Float!
  totalPayments: Float!
  outstandingAmount: Float!
}

type StudentPerformanceReport {
  assignmentTitle: String!
  grade: Float!
}

type Query {
  academicPerformanceReport(courseId: Int!): [AcademicPerformanceReport!]!
  attendanceReport: [AttendanceReport!]!
  financialReport: FinancialReport!
  studentPerformanceReport(studentId: Int!): [StudentPerformanceReport!]!
}
Enter fullscreen mode Exit fullscreen mode

This setup adds graphs for better visualization of reports and includes an additional report type for student performance. You can further expand this by adding more report types as needed.

If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!

Disclaimer: This content in generated by AI.

Top comments (0)