Detail implementation of Reports and Analytics
Published: 2024-06-13 15:19:36

Sure! 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:**
```typescript
// 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,
    };
  }
}
```

#### 3. Resolvers

**Report Resolver:**
```typescript
// 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();
  }
}
```

### Frontend (Next.js)

#### 1. Apollo Client Setup
```javascript
// apollo-client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

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

export default client;
```

#### 2. Academic Performance Report Page
```javascript
// 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>
  );
}
```

#### 3. Attendance Report Page
```javascript
// 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>
  );
}
```

#### 4. Financial Report Page
```javascript
// 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>
  );
}
```

### GraphQL Schema
Define your GraphQL schema to match the resolver functions:

```graphql
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!
}
```

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`:

```bash
npm install chart.js react-chartjs-2
```

#### 2. Update Academic Performance Report Page

**pages/reports/academic-performance.js**
```javascript
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>
  );
}
```

#### 3. Update Attendance Report Page

**pages/reports/attendance.js**
```javascript
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>
  );
}
```

#### 4. Update Financial Report Page

**pages/reports/financial.js**
```javascript
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>
  );
}
```

### 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:**
```typescript
// 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;
  }
}
```

#### 2. Resolver

**Report Resolver:**
```typescript
// 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);
  }
}
```

### Frontend (Next.js)

#### 1. Student Performance Report Page

**pages/reports/student-performance.js**
```javascript
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>
  );
}
```

### GraphQL Schema

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

```graphql
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!]!
}
```

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.

Disclaimer: This content in generated by AI.