DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on • Updated on

Detail implementation of Learning Management System

Below is an implementation for a Learning Management System (LMS) focusing on online course management, assignment submission, and virtual classroom features using Next.js, NestJS, and GraphQL.

Backend (NestJS)

1. Entities

Course Entity:

// course.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { Assignment } from './assignment.entity';

@Entity()
export class Course {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  description: string;

  @OneToMany(() => Assignment, (assignment) => assignment.course)
  assignments: Assignment[];
}
Enter fullscreen mode Exit fullscreen mode

Assignment Entity:

// assignment.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Course } from './course.entity';

@Entity()
export class Assignment {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column()
  dueDate: Date;

  @ManyToOne(() => Course, (course) => course.assignments)
  course: Course;
}
Enter fullscreen mode Exit fullscreen mode

Submission Entity:

// submission.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Assignment } from './assignment.entity';
import { User } from './user.entity';

@Entity()
export class Submission {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => Assignment, (assignment) => assignment.submissions)
  assignment: Assignment;

  @ManyToOne(() => User, (user) => user.submissions)
  student: User;

  @Column()
  content: string;

  @Column()
  submittedAt: Date;
}
Enter fullscreen mode Exit fullscreen mode

2. Services

Course Service:

// course.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Course } from './course.entity';

@Injectable()
export class CourseService {
  constructor(
    @InjectRepository(Course)
    private courseRepository: Repository<Course>,
  ) {}

  findAll(): Promise<Course[]> {
    return this.courseRepository.find({ relations: ['assignments'] });
  }

  findOne(id: number): Promise<Course> {
    return this.courseRepository.findOne(id, { relations: ['assignments'] });
  }

  create(name: string, description: string): Promise<Course> {
    const newCourse = this.courseRepository.create({ name, description });
    return this.courseRepository.save(newCourse);
  }
}
Enter fullscreen mode Exit fullscreen mode

Assignment Service:

// assignment.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Assignment } from './assignment.entity';

@Injectable()
export class AssignmentService {
  constructor(
    @InjectRepository(Assignment)
    private assignmentRepository: Repository<Assignment>,
  ) {}

  findAll(): Promise<Assignment[]> {
    return this.assignmentRepository.find({ relations: ['course'] });
  }

  findOne(id: number): Promise<Assignment> {
    return this.assignmentRepository.findOne(id, { relations: ['course'] });
  }

  create(title: string, description: string, dueDate: Date, courseId: number): Promise<Assignment> {
    const newAssignment = this.assignmentRepository.create({ title, description, dueDate, course: { id: courseId } });
    return this.assignmentRepository.save(newAssignment);
  }
}
Enter fullscreen mode Exit fullscreen mode

Submission Service:

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

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

  findAll(): Promise<Submission[]> {
    return this.submissionRepository.find({ relations: ['assignment', 'student'] });
  }

  findOne(id: number): Promise<Submission> {
    return this.submissionRepository.findOne(id, { relations: ['assignment', 'student'] });
  }

  create(content: string, assignmentId: number, studentId: number): Promise<Submission> {
    const newSubmission = this.submissionRepository.create({
      content,
      submittedAt: new Date(),
      assignment: { id: assignmentId },
      student: { id: studentId },
    });
    return this.submissionRepository.save(newSubmission);
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Resolvers

Course Resolver:

// course.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { CourseService } from './course.service';
import { Course } from './course.entity';

@Resolver(() => Course)
export class CourseResolver {
  constructor(private courseService: CourseService) {}

  @Query(() => [Course])
  async courses() {
    return this.courseService.findAll();
  }

  @Mutation(() => Course)
  async createCourse(
    @Args('name') name: string,
    @Args('description') description: string,
  ) {
    return this.courseService.create(name, description);
  }
}
Enter fullscreen mode Exit fullscreen mode

Assignment Resolver:

// assignment.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { AssignmentService } from './assignment.service';
import { Assignment } from './assignment.entity';

@Resolver(() => Assignment)
export class AssignmentResolver {
  constructor(private assignmentService: AssignmentService) {}

  @Query(() => [Assignment])
  async assignments() {
    return this.assignmentService.findAll();
  }

  @Mutation(() => Assignment)
  async createAssignment(
    @Args('title') title: string,
    @Args('description') description: string,
    @Args('dueDate') dueDate: string,
    @Args('courseId') courseId: number,
  ) {
    return this.assignmentService.create(title, description, new Date(dueDate), courseId);
  }
}
Enter fullscreen mode Exit fullscreen mode

Submission Resolver:

// submission.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { SubmissionService } from './submission.service';
import { Submission } from './submission.entity';

@Resolver(() => Submission)
export class SubmissionResolver {
  constructor(private submissionService: SubmissionService) {}

  @Query(() => [Submission])
  async submissions() {
    return this.submissionService.findAll();
  }

  @Mutation(() => Submission)
  async createSubmission(
    @Args('content') content: string,
    @Args('assignmentId') assignmentId: number,
    @Args('studentId') studentId: number,
  ) {
    return this.submissionService.create(content, assignmentId, studentId);
  }
}
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. Course Management Page

// pages/courses.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_COURSES = gql`
  query GetCourses {
    courses {
      id
      name
      description
      assignments {
        id
        title
        dueDate
      }
    }
  }
`;

const CREATE_COURSE = gql`
  mutation CreateCourse($name: String!, $description: String!) {
    createCourse(name: $name, description: $description) {
      id
      name
      description
    }
  }
`;

export default function Courses() {
  const { loading, error, data } = useQuery(GET_COURSES);
  const [createCourse] = useMutation(CREATE_COURSE);
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createCourse({ variables: { name, description } });
    setName('');
    setDescription('');
  };

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

  return (
    <div>
      <h1>Courses</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Course Name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <textarea
          placeholder="Course Description"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
        ></textarea>
        <button type="submit">Create Course</button>
      </form>
      <ul>
        {data.courses.map((course) => (
          <li key={course.id}>
            <h2>{course.name}</h2>
            <p>{course.description}</p>
            <h3>Assignments</h3>
            <ul>
              {course.assignments.map((assignment) => (
                <li key={assignment.id}>
                  {assignment.title} - Due: {assignment.dueDate}
                </li>
              ))}
            </ul>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Assignment Management

Page

// pages/assignments.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_ASSIGNMENTS = gql`
  query GetAssignments {
    assignments {
      id
      title
      description
      dueDate
      course {
        name
      }
    }
  }
`;

const CREATE_ASSIGNMENT = gql`
  mutation CreateAssignment($title: String!, $description: String!, $dueDate: String!, $courseId: Int!) {
    createAssignment(title: $title, description: $description, dueDate: $dueDate, courseId: $courseId) {
      id
      title
      description
      dueDate
    }
  }
`;

export default function Assignments() {
  const { loading, error, data } = useQuery(GET_ASSIGNMENTS);
  const [createAssignment] = useMutation(CREATE_ASSIGNMENT);
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [dueDate, setDueDate] = useState('');
  const [courseId, setCourseId] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createAssignment({ variables: { title, description, dueDate, courseId: parseInt(courseId) } });
    setTitle('');
    setDescription('');
    setDueDate('');
    setCourseId('');
  };

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

  return (
    <div>
      <h1>Assignments</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
        <textarea
          placeholder="Description"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
        ></textarea>
        <input
          type="date"
          placeholder="Due Date"
          value={dueDate}
          onChange={(e) => setDueDate(e.target.value)}
        />
        <input
          type="number"
          placeholder="Course ID"
          value={courseId}
          onChange={(e) => setCourseId(e.target.value)}
        />
        <button type="submit">Create Assignment</button>
      </form>
      <ul>
        {data.assignments.map((assignment) => (
          <li key={assignment.id}>
            <h2>{assignment.title}</h2>
            <p>{assignment.description}</p>
            <p>Due Date: {assignment.dueDate}</p>
            <p>Course: {assignment.course.name}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Submission Management Page

// pages/submissions.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_SUBMISSIONS = gql`
  query GetSubmissions {
    submissions {
      id
      content
      submittedAt
      assignment {
        title
      }
      student {
        username
      }
    }
  }
`;

const CREATE_SUBMISSION = gql`
  mutation CreateSubmission($content: String!, $assignmentId: Int!, $studentId: Int!) {
    createSubmission(content: $content, assignmentId: $assignmentId, studentId: $studentId) {
      id
      content
      submittedAt
    }
  }
`;

export default function Submissions() {
  const { loading, error, data } = useQuery(GET_SUBMISSIONS);
  const [createSubmission] = useMutation(CREATE_SUBMISSION);
  const [content, setContent] = useState('');
  const [assignmentId, setAssignmentId] = useState('');
  const [studentId, setStudentId] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createSubmission({ variables: { content, assignmentId: parseInt(assignmentId), studentId: parseInt(studentId) } });
    setContent('');
    setAssignmentId('');
    setStudentId('');
  };

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

  return (
    <div>
      <h1>Submissions</h1>
      <form onSubmit={handleSubmit}>
        <textarea
          placeholder="Content"
          value={content}
          onChange={(e) => setContent(e.target.value)}
        ></textarea>
        <input
          type="number"
          placeholder="Assignment ID"
          value={assignmentId}
          onChange={(e) => setAssignmentId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Student ID"
          value={studentId}
          onChange={(e) => setStudentId(e.target.value)}
        />
        <button type="submit">Submit Assignment</button>
      </form>
      <ul>
        {data.submissions.map((submission) => (
          <li key={submission.id}>
            <h2>{submission.assignment.title}</h2>
            <p>Submitted by: {submission.student.username}</p>
            <p>Submitted at: {submission.submittedAt}</p>
            <p>Content: {submission.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Virtual Classroom (Integration)

For virtual classroom functionality, you can integrate a service like Zoom, Google Meet, or any other preferred video conferencing tool. You will need to manage scheduling and links to these virtual classes.

Virtual Class Entity:

// virtual-class.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Course } from './course.entity';

@Entity()
export class VirtualClass {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  meetingLink: string;

  @Column()
  schedule: Date;

  @ManyToOne(() => Course, (course) => course.virtualClasses)
  course: Course;
}
Enter fullscreen mode Exit fullscreen mode

Virtual Class Service:

// virtual-class.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { VirtualClass } from './virtual-class.entity';

@Injectable()
export class VirtualClassService {
  constructor(
    @InjectRepository(VirtualClass)
    private virtualClassRepository: Repository<VirtualClass>,
  ) {}

  findAll(): Promise<VirtualClass[]> {
    return this.virtualClassRepository.find({ relations: ['course'] });
  }

  create(meetingLink: string, schedule: Date, courseId: number): Promise<VirtualClass> {
    const newVirtualClass = this.virtualClassRepository.create({ meetingLink, schedule, course: { id: courseId } });
    return this.virtualClassRepository.save(newVirtualClass);
  }
}
Enter fullscreen mode Exit fullscreen mode

Virtual Class Resolver:

// virtual-class.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { VirtualClassService } from './virtual-class.service';
import { VirtualClass } from './virtual-class.entity';

@Resolver(() => VirtualClass)
export class VirtualClassResolver {
  constructor(private virtualClassService: VirtualClassService) {}

  @Query(() => [VirtualClass])
  async virtualClasses() {
    return this.virtualClassService.findAll();
  }

  @Mutation(() => VirtualClass)
  async createVirtualClass(
    @Args('meetingLink') meetingLink: string,
    @Args('schedule') schedule: string,
    @Args('courseId') courseId: number,
  ) {
    return this.virtualClassService.create(meetingLink, new Date(schedule), courseId);
  }
}
Enter fullscreen mode Exit fullscreen mode

Virtual Classroom Management Page:

// pages/virtual-classes.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_VIRTUAL_CLASSES = gql`
  query GetVirtualClasses {
    virtualClasses {
      id
      meetingLink
      schedule
      course {
        name
      }
    }
  }
`;

const CREATE_VIRTUAL_CLASS = gql`
  mutation CreateVirtualClass($meetingLink: String!, $schedule: String!, $courseId: Int!) {
    createVirtualClass(meetingLink: $meetingLink, schedule: $schedule, courseId: $courseId) {
      id
      meetingLink
      schedule
    }
  }
`;

export default function VirtualClasses() {
  const { loading, error, data } = useQuery(GET_VIRTUAL_CLASSES);
  const [createVirtualClass] = useMutation(CREATE_VIRTUAL_CLASS);
  const [meetingLink, setMeetingLink] = useState('');
  const [schedule, setSchedule] = useState('');
  const [courseId, setCourseId] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createVirtualClass({ variables: { meetingLink, schedule, courseId: parseInt(courseId) } });
    setMeetingLink('');
    setSchedule('');
    setCourseId('');
  };

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

  return (
    <div>
      <h1>Virtual Classes</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Meeting Link"
          value={meetingLink}
          onChange={(e

) => setMeetingLink(e.target.value)}
        />
        <input
          type="datetime-local"
          placeholder="Schedule"
          value={schedule}
          onChange={(e) => setSchedule(e.target.value)}
        />
        <input
          type="number"
          placeholder="Course ID"
          value={courseId}
          onChange={(e) => setCourseId(e.target.value)}
        />
        <button type="submit">Create Virtual Class</button>
      </form>
      <ul>
        {data.virtualClasses.map((virtualClass) => (
          <li key={virtualClass.id}>
            <p>Meeting Link: <a href={virtualClass.meetingLink} target="_blank" rel="noopener noreferrer">Join</a></p>
            <p>Schedule: {virtualClass.schedule}</p>
            <p>Course: {virtualClass.course.name}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Schema

Define your GraphQL schema to match the resolver functions:

type User {
  id: ID!
  username: String!
}

type Course {
  id: ID!
  name: String!
  description: String!
  assignments: [Assignment!]!
}

type Assignment {
  id: ID!
  title: String!
  description: String!
  dueDate: String!
  course: Course!
}

type Submission {
  id: ID!
  content: String!
  submittedAt: String!
  assignment: Assignment!
  student: User!
}

type VirtualClass {
  id: ID!
  meetingLink: String!
  schedule: String!
  course: Course!
}

type Query {
  courses: [Course!]!
  assignments: [Assignment!]!
  submissions: [Submission!]!
  virtualClasses: [VirtualClass!]!
}

type Mutation {
  createCourse(name: String!, description: String!): Course!
  createAssignment(title: String!, description: String!, dueDate: String!, courseId: Int!): Assignment!
  createSubmission(content: String!, assignmentId: Int!, studentId: Int!): Submission!
  createVirtualClass(meetingLink: String!, schedule: String!, courseId: Int!): VirtualClass!
}
Enter fullscreen mode Exit fullscreen mode

This setup covers the backend and frontend code for developing online course management, assignment submission, and virtual classroom features. You can expand on this by adding more features such as grading, feedback, and advanced scheduling for virtual classes.

Adding Grading, Feedback, and Advanced Scheduling

To add grading, feedback, and advanced scheduling for virtual classes, we'll update the existing system to include these features.

Backend (NestJS)

1. Update Entities

Assignment Entity:

// assignment.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, OneToMany } from 'typeorm';
import { Course } from './course.entity';
import { Submission } from './submission.entity';

@Entity()
export class Assignment {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column()
  dueDate: Date;

  @ManyToOne(() => Course, (course) => course.assignments)
  course: Course;

  @OneToMany(() => Submission, (submission) => submission.assignment)
  submissions: Submission[];
}
Enter fullscreen mode Exit fullscreen mode

Submission Entity:

// submission.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Assignment } from './assignment.entity';
import { User } from './user.entity';

@Entity()
export class Submission {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => Assignment, (assignment) => assignment.submissions)
  assignment: Assignment;

  @ManyToOne(() => User, (user) => user.submissions)
  student: User;

  @Column()
  content: string;

  @Column({ nullable: true })
  grade: number;

  @Column({ nullable: true })
  feedback: string;

  @Column()
  submittedAt: Date;
}
Enter fullscreen mode Exit fullscreen mode

Virtual Class Entity:

// virtual-class.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Course } from './course.entity';

@Entity()
export class VirtualClass {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  meetingLink: string;

  @Column()
  schedule: Date;

  @ManyToOne(() => Course, (course) => course.virtualClasses)
  course: Course;
}
Enter fullscreen mode Exit fullscreen mode

2. Update Services

Assignment Service:

// assignment.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Assignment } from './assignment.entity';

@Injectable()
export class AssignmentService {
  constructor(
    @InjectRepository(Assignment)
    private assignmentRepository: Repository<Assignment>,
  ) {}

  findAll(): Promise<Assignment[]> {
    return this.assignmentRepository.find({ relations: ['course', 'submissions'] });
  }

  findOne(id: number): Promise<Assignment> {
    return this.assignmentRepository.findOne(id, { relations: ['course', 'submissions'] });
  }

  create(title: string, description: string, dueDate: Date, courseId: number): Promise<Assignment> {
    const newAssignment = this.assignmentRepository.create({ title, description, dueDate, course: { id: courseId } });
    return this.assignmentRepository.save(newAssignment);
  }

  gradeSubmission(submissionId: number, grade: number, feedback: string): Promise<Assignment> {
    return this.assignmentRepository.update({ id: submissionId }, { grade, feedback });
  }
}
Enter fullscreen mode Exit fullscreen mode

Virtual Class Service:

// virtual-class.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { VirtualClass } from './virtual-class.entity';

@Injectable()
export class VirtualClassService {
  constructor(
    @InjectRepository(VirtualClass)
    private virtualClassRepository: Repository<VirtualClass>,
  ) {}

  findAll(): Promise<VirtualClass[]> {
    return this.virtualClassRepository.find({ relations: ['course'] });
  }

  create(meetingLink: string, schedule: Date, courseId: number): Promise<VirtualClass> {
    const newVirtualClass = this.virtualClassRepository.create({ meetingLink, schedule, course: { id: courseId } });
    return this.virtualClassRepository.save(newVirtualClass);
  }

  updateSchedule(id: number, schedule: Date): Promise<VirtualClass> {
    return this.virtualClassRepository.update(id, { schedule });
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Update Resolvers

Assignment Resolver:

// assignment.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { AssignmentService } from './assignment.service';
import { Assignment } from './assignment.entity';

@Resolver(() => Assignment)
export class AssignmentResolver {
  constructor(private assignmentService: AssignmentService) {}

  @Query(() => [Assignment])
  async assignments() {
    return this.assignmentService.findAll();
  }

  @Mutation(() => Assignment)
  async createAssignment(
    @Args('title') title: string,
    @Args('description') description: string,
    @Args('dueDate') dueDate: string,
    @Args('courseId') courseId: number,
  ) {
    return this.assignmentService.create(title, description, new Date(dueDate), courseId);
  }

  @Mutation(() => Assignment)
  async gradeSubmission(
    @Args('submissionId') submissionId: number,
    @Args('grade') grade: number,
    @Args('feedback') feedback: string,
  ) {
    return this.assignmentService.gradeSubmission(submissionId, grade, feedback);
  }
}
Enter fullscreen mode Exit fullscreen mode

Virtual Class Resolver:

// virtual-class.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { VirtualClassService } from './virtual-class.service';
import { VirtualClass } from './virtual-class.entity';

@Resolver(() => VirtualClass)
export class VirtualClassResolver {
  constructor(private virtualClassService: VirtualClassService) {}

  @Query(() => [VirtualClass])
  async virtualClasses() {
    return this.virtualClassService.findAll();
  }

  @Mutation(() => VirtualClass)
  async createVirtualClass(
    @Args('meetingLink') meetingLink: string,
    @Args('schedule') schedule: string,
    @Args('courseId') courseId: number,
  ) {
    return this.virtualClassService.create(meetingLink, new Date(schedule), courseId);
  }

  @Mutation(() => VirtualClass)
  async updateSchedule(
    @Args('id') id: number,
    @Args('schedule') schedule: string,
  ) {
    return this.virtualClassService.updateSchedule(id, new Date(schedule));
  }
}
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. Update Assignment Management Page

// pages/assignments.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_ASSIGNMENTS = gql`
  query GetAssignments {
    assignments {
      id
      title
      description
      dueDate
      course {
        name
      }
      submissions {
        id
        content
        submittedAt
        grade
        feedback
        student {
          username
        }
      }
    }
  }
`;

const CREATE_ASSIGNMENT = gql`
  mutation CreateAssignment($title: String!, $description: String!, $dueDate: String!, $courseId: Int!) {
    createAssignment(title: $title, description: $description, dueDate: $dueDate, courseId: $courseId) {
      id
      title
      description
      dueDate
    }
  }
`;

const GRADE_SUBMISSION = gql`
  mutation GradeSubmission($submissionId: Int!, $grade: Int!, $feedback: String!) {
    gradeSubmission(submissionId: $submissionId, grade: $grade, feedback: $feedback) {
      id
      grade
      feedback
    }
  }
`;

export default function Assignments() {
  const { loading, error, data } = useQuery(GET_ASSIGNMENTS);
  const [createAssignment] = useMutation(CREATE_ASSIGNMENT);
  const [gradeSubmission] = useMutation(GRADE_SUBMISSION);
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [dueDate, setDueDate] = useState('');
  const [courseId, setCourseId] = useState('');
  const [submissionId, setSubmissionId] = useState('');
  const [grade, setGrade] = useState('');
  const [feedback, setFeedback] = useState('');

  const handleCreateAssignment = async (e) => {
    e.preventDefault();
    await createAssignment({ variables: { title, description, dueDate, courseId: parseInt(courseId) } });
    setTitle('');
    setDescription('');
    setDueDate('');
    setCourseId('');
  };

  const handleGradeSubmission = async (e) => {
    e.preventDefault();
    await gradeSubmission({ variables: { submissionId: parseInt(submissionId), grade: parseInt(grade), feedback } });
    setSubmissionId('');
    setGrade('');
    setFeedback('');
  };

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

  return (
    <div>
      <h1>Assignments</h1>
      <form onSubmit={handleCreateAssignment}>
        <input


 type="text"
          placeholder="Title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
        <textarea
          placeholder="Description"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
        ></textarea>
        <input
          type="date"
          placeholder="Due Date"
          value={dueDate}
          onChange={(e) => setDueDate(e.target.value)}
        />
        <input
          type="number"
          placeholder="Course ID"
          value={courseId}
          onChange={(e) => setCourseId(e.target.value)}
        />
        <button type="submit">Create Assignment</button>
      </form>
      <form onSubmit={handleGradeSubmission}>
        <input
          type="number"
          placeholder="Submission ID"
          value={submissionId}
          onChange={(e) => setSubmissionId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Grade"
          value={grade}
          onChange={(e) => setGrade(e.target.value)}
        />
        <textarea
          placeholder="Feedback"
          value={feedback}
          onChange={(e) => setFeedback(e.target.value)}
        ></textarea>
        <button type="submit">Grade Submission</button>
      </form>
      <ul>
        {data.assignments.map((assignment) => (
          <li key={assignment.id}>
            <h2>{assignment.title}</h2>
            <p>{assignment.description}</p>
            <p>Due Date: {assignment.dueDate}</p>
            <p>Course: {assignment.course.name}</p>
            <h3>Submissions</h3>
            <ul>
              {assignment.submissions.map((submission) => (
                <li key={submission.id}>
                  <p>Submitted by: {submission.student.username}</p>
                  <p>Submitted at: {submission.submittedAt}</p>
                  <p>Content: {submission.content}</p>
                  <p>Grade: {submission.grade}</p>
                  <p>Feedback: {submission.feedback}</p>
                </li>
              ))}
            </ul>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Update Virtual Classroom Management Page

// pages/virtual-classes.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_VIRTUAL_CLASSES = gql`
  query GetVirtualClasses {
    virtualClasses {
      id
      meetingLink
      schedule
      course {
        name
      }
    }
  }
`;

const CREATE_VIRTUAL_CLASS = gql`
  mutation CreateVirtualClass($meetingLink: String!, $schedule: String!, $courseId: Int!) {
    createVirtualClass(meetingLink: $meetingLink, schedule: $schedule, courseId: $courseId) {
      id
      meetingLink
      schedule
    }
  }
`;

const UPDATE_SCHEDULE = gql`
  mutation UpdateSchedule($id: Int!, $schedule: String!) {
    updateSchedule(id: $id, schedule: $schedule) {
      id
      schedule
    }
  }
`;

export default function VirtualClasses() {
  const { loading, error, data } = useQuery(GET_VIRTUAL_CLASSES);
  const [createVirtualClass] = useMutation(CREATE_VIRTUAL_CLASS);
  const [updateSchedule] = useMutation(UPDATE_SCHEDULE);
  const [meetingLink, setMeetingLink] = useState('');
  const [schedule, setSchedule] = useState('');
  const [courseId, setCourseId] = useState('');
  const [classId, setClassId] = useState('');

  const handleCreateVirtualClass = async (e) => {
    e.preventDefault();
    await createVirtualClass({ variables: { meetingLink, schedule, courseId: parseInt(courseId) } });
    setMeetingLink('');
    setSchedule('');
    setCourseId('');
  };

  const handleUpdateSchedule = async (e) => {
    e.preventDefault();
    await updateSchedule({ variables: { id: parseInt(classId), schedule } });
    setClassId('');
    setSchedule('');
  };

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

  return (
    <div>
      <h1>Virtual Classes</h1>
      <form onSubmit={handleCreateVirtualClass}>
        <input
          type="text"
          placeholder="Meeting Link"
          value={meetingLink}
          onChange={(e) => setMeetingLink(e.target.value)}
        />
        <input
          type="datetime-local"
          placeholder="Schedule"
          value={schedule}
          onChange={(e) => setSchedule(e.target.value)}
        />
        <input
          type="number"
          placeholder="Course ID"
          value={courseId}
          onChange={(e) => setCourseId(e.target.value)}
        />
        <button type="submit">Create Virtual Class</button>
      </form>
      <form onSubmit={handleUpdateSchedule}>
        <input
          type="number"
          placeholder="Class ID"
          value={classId}
          onChange={(e) => setClassId(e.target.value)}
        />
        <input
          type="datetime-local"
          placeholder="New Schedule"
          value={schedule}
          onChange={(e) => setSchedule(e.target.value)}
        />
        <button type="submit">Update Schedule</button>
      </form>
      <ul>
        {data.virtualClasses.map((virtualClass) => (
          <li key={virtualClass.id}>
            <p>Meeting Link: <a href={virtualClass.meetingLink} target="_blank" rel="noopener noreferrer">Join</a></p>
            <p>Schedule: {virtualClass.schedule}</p>
            <p>Course: {virtualClass.course.name}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Schema

Update your GraphQL schema to include the new fields and mutations:

type User {
  id: ID!
  username: String!
}

type Course {
  id: ID!
  name: String!
  description: String!
  assignments: [Assignment!]!
}

type Assignment {
  id: ID!
  title: String!
  description: String!
  dueDate: String!
  course: Course!
  submissions: [Submission!]!
}

type Submission {
  id: ID!
  content: String!
  submittedAt: String!
  grade: Int
  feedback: String
  assignment: Assignment!
  student: User!
}

type VirtualClass {
  id: ID!
  meetingLink: String!
  schedule: String!
  course: Course!
}

type Query {
  courses: [Course!]!
  assignments: [Assignment!]!
  submissions: [Submission!]!
  virtualClasses: [VirtualClass!]!
}

type Mutation {
  createCourse(name: String!, description: String!): Course!
  createAssignment(title: String!, description: String!, dueDate: String!, courseId: Int!): Assignment!
  createSubmission(content: String!, assignmentId: Int!, studentId: Int!): Submission!
  gradeSubmission(submissionId: Int!, grade: Int!, feedback: String!): Submission!
  createVirtualClass(meetingLink: String!, schedule: String!, courseId: Int!): VirtualClass!
  updateSchedule(id: Int!, schedule: String!): VirtualClass!
}
Enter fullscreen mode Exit fullscreen mode

This expanded setup includes grading, feedback, and advanced scheduling for virtual classes, covering the backend and frontend code needed to implement these features. You can further enhance these features by adding notifications, more detailed reports, and improved user interfaces.

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)