Skip to main content

Overview

The Educator Dashboard provides a comprehensive view of your teaching performance, revenue, and student engagement. Access it at /educator/dashboard.

Dashboard Layout

The dashboard is organized into three main sections:
1

Stat Cards

High-level metrics showing total enrollments, courses, and earnings
2

Latest Enrollments

Table of recent student registrations across all courses
3

Quiz Insights

Student performance data on chapter quizzes (if available)

Key Metrics

Stat Cards Overview

Three primary metrics are displayed at the top:

Total Enrollments

Count of all students enrolled across all your courses

Published Courses

Number of courses you’ve created and published

Total Earnings

Cumulative revenue from all completed course purchases (₹)

Data Fetching

Metrics are fetched from the backend:
const fetchDashboardData = async () => {
  try {
    const token = await getToken()
    const { data } = await axios.get(
      backendUrl + '/api/educator/dashboard',
      {
        headers: { Authorization: `Bearer ${token}` }
      }
    )
    if (data.success) {
      setDashboardData(data.dashboardData)
    }
  } catch (error) {
    toast.error(error.message)
  }
}

Backend Calculation

The server calculates these metrics:
// Get all educator's courses
const courses = await Course.find({ educatorId })
const totalCourses = courses.length

// Get course IDs
const courseIds = courses.map((course) => course._id)

// Calculate total earnings from completed purchases
const purchases = await Purchase.find({
  courseId: { $in: courseIds },
  status: 'completed'
})

const totalEarnings = purchases.reduce(
  (sum, purchase) => sum + purchase.amount,
  0
)

// Collect enrolled students with course info
const allStudentIds = courses.flatMap(
  (course) => course.enrolledStudents
)
const allStudents = await User.find(
  { _id: { $in: allStudentIds } },
  'name imageUrl'
)

// Map students to their courses
const enrolledStudentsData = courses.flatMap((course) =>
  course.enrolledStudents
    .map((studentId) => studentMap[studentId.toString()])
    .filter(Boolean)
    .map((student) => ({
      courseTitle: course.courseTitle,
      student
    }))
)
Only completed purchases count toward earnings. Pending or failed transactions are excluded.

Total Enrollments

What It Shows

The total number of unique student enrollments across all your courses.
const totalEnrollments = dashboardData.enrolledStudentsData.length
This counts all enrollment records, where each student-course pair is one enrollment.

Display Component

<StatCard
  iconBg="bg-teal-50"
  iconColor="text-teal-600"
  icon={<UsersIcon />}
  label="Total Enrollments"
  value={dashboardData.enrolledStudentsData.length}
/>

Published Courses

What It Shows

The count of courses you’ve created, regardless of enrollment status.
const totalCourses = courses.length

// All courses where educatorId matches your user ID
const courses = await Course.find({ educatorId })
This includes courses with zero enrollments. To see detailed course info, visit the “My Courses” page.

Total Earnings

Revenue Calculation

Earnings are calculated from completed purchases only:
// Backend calculation
const purchases = await Purchase.find({
  courseId: { $in: courseIds },
  status: 'completed' // ✓ Only completed
})

const totalEarnings = purchases.reduce(
  (sum, purchase) => sum + purchase.amount,
  0
)

Purchase Model

const Purchase = {
  userId: String,        // Student who purchased
  courseId: ObjectId,    // Course purchased
  amount: Number,        // Actual price paid (after discount)
  status: String,        // 'completed', 'pending', 'failed'
  createdAt: Date        // Purchase timestamp
}

Price Calculation per Purchase

// The amount stored is the final price after discount
const coursePrice = 4999
const discount = 20
const finalPrice = Math.floor(
  coursePrice - (discount * coursePrice / 100)
)
// finalPrice = 3999 (this is what gets stored in Purchase.amount)

Display Format

<StatCard
  iconBg="bg-amber-50"
  iconColor="text-amber-600"
  icon={<RupeeIcon />}
  label="Total Earnings"
  value={`₹${dashboardData.totalEarnings.toLocaleString()}`}
/>

// Example output: ₹1,23,456

Latest Enrollments Table

A detailed view of student enrollments across all courses:

Table Structure

enrolledStudentsData
array
Array of enrollment objects

Table Columns

ColumnDescriptionResponsive
#Sequential number (1, 2, 3…)Hidden on mobile
StudentName and profile pictureAlways visible
CourseCourse title (truncated if long)Always visible

Implementation

<table className="w-full">
  <thead>
    <tr>
      <th className="hidden sm:table-cell">#</th>
      <th>Student</th>
      <th>Course</th>
    </tr>
  </thead>
  <tbody>
    {dashboardData.enrolledStudentsData.map((item, index) => (
      <tr key={index}>
        <td className="hidden sm:table-cell">
          {index + 1}
        </td>
        <td>
          <div className="flex items-center gap-3">
            <img
              src={item.student.imageUrl}
              alt=""
              className="w-8 h-8 rounded-full"
            />
            <span>{item.student.name}</span>
          </div>
        </td>
        <td className="max-w-xs truncate">
          {item.courseTitle}
        </td>
      </tr>
    ))}
  </tbody>
</table>

Empty State

When no students have enrolled:
{dashboardData.enrolledStudentsData.length === 0 && (
  <div className="py-14 text-center">
    <p className="text-gray-400 text-sm">No enrollments yet</p>
  </div>
)}

Quiz Insights

Quiz insights only appear if you’ve added quizzes to your course chapters and students have taken them.

What It Shows

Student performance data across chapter quizzes:
  • Average score percentage
  • Number of quiz attempts
  • Performance distribution (Needs Review, On Track, Mastered)

Data Fetching

const fetchQuizInsights = async () => {
  try {
    const token = await getToken()
    const { data } = await axios.get(
      backendUrl + '/api/quiz/educator-insights',
      {
        headers: { Authorization: `Bearer ${token}` }
      }
    )
    if (data.success) {
      setQuizInsights(data.insights)
    }
  } catch {
    // Non-critical, fail silently
  }
}

Insight Structure

const insight = {
  courseTitle: "React Fundamentals",
  chapterTitle: "State Management",
  attempts: 25,           // Total quiz attempts
  avgPct: 78,            // Average score percentage
  needs_review: 3,       // Students scoring < 60%
  on_track: 12,          // Students scoring 60-85%
  mastered: 10           // Students scoring > 85%
}

Performance Categories

Students struggling with the material
  • Score: Below 60%
  • Color: Red
  • Action: Consider reviewing this chapter content
  • May need additional resources or clarification

Visual Representation

Quiz insights display a stacked bar chart:
<div className="flex h-2 rounded-full overflow-hidden gap-px">
  {['needs_review', 'on_track', 'mastered'].map((category) => {
    const percentage = (item[category] / total) * 100
    return (
      <div
        key={category}
        className={`${GROUP_COLORS[category].bar}`}
        style={{ width: `${percentage}%` }}
        title={`${GROUP_COLORS[category].label}: ${item[category]} students`}
      />
    )
  })}
</div>
Color Scheme:
const GROUP_COLORS = {
  needs_review: {
    bar: 'bg-red-400',
    label: 'Needs Review',
    text: 'text-red-600',
    bg: 'bg-red-50'
  },
  on_track: {
    bar: 'bg-amber-400',
    label: 'On Track',
    text: 'text-amber-600',
    bg: 'bg-amber-50'
  },
  mastered: {
    bar: 'bg-teal-500',
    label: 'Mastered',
    text: 'text-teal-600',
    bg: 'bg-teal-50'
  }
}

Example Quiz Insight Display

┌─────────────────────────────────────────────────┐
│ State Management                           78%  │
│ React Fundamentals                     25 attempts│
├─────────────────────────────────────────────────┤
│ [====|================|==========]             │
│  Red     Amber          Teal                   │
├─────────────────────────────────────────────────┤
│ 🔴 Needs Review: 3                              │
│ 🟡 On Track: 12                                 │
│ 🟢 Mastered: 10                                 │
└─────────────────────────────────────────────────┘

Loading States

While data is being fetched, skeleton loaders are displayed:
{!dashboardData ? (
  <div className="space-y-8">
    {/* Stat card skeletons */}
    <div className="flex flex-wrap gap-4">
      {[...Array(3)].map((_, i) => (
        <div key={i} className="flex-1 min-w-[190px]">
          <Skeleton className="h-11 w-11 rounded-xl" />
          <Skeleton className="h-7 w-20 mt-2" />
          <Skeleton className="h-3 w-28 mt-1" />
        </div>
      ))}
    </div>
    
    {/* Table skeleton */}
    <div className="space-y-2">
      {[...Array(5)].map((_, i) => (
        <div key={i} className="flex items-center gap-4 p-4">
          <Skeleton className="w-8 h-8 rounded-full" />
          <Skeleton className="h-4 w-32" />
          <Skeleton className="h-4 w-48 ml-auto" />
        </div>
      ))}
    </div>
  </div>
) : (
  // ... actual dashboard content
)}
The loading state ensures a smooth user experience while data is being retrieved from the backend.

Responsive Design

The dashboard adapts to different screen sizes:

Desktop (≥768px)

  • All columns visible
  • Stat cards in horizontal row
  • Full table with all columns

Tablet (640px-767px)

  • Sequential numbers hidden
  • Stat cards wrap to 2 columns
  • Table scrolls horizontally if needed

Mobile (below 640px)

  • Stat cards stack vertically
  • Simplified table (student + course only)
  • Condensed quiz insights
// Responsive class example
<th className="hidden sm:table-cell"> // Hidden on mobile
  #
</th>

Real-Time Updates

When Data Refreshes

Dashboard data is fetched:
  1. On page load - useEffect hook triggers fetch
  2. When educator status changes - Auth context update
  3. Manual refresh - Browser page reload
useEffect(() => {
  if (isEducator) {
    fetchDashboardData()
    fetchQuizInsights()
  }
}, [isEducator])
The dashboard does not auto-refresh. Students enrolling while you’re viewing the page won’t appear until you reload.

Adding Auto-Refresh (Optional)

To implement auto-refresh:
useEffect(() => {
  if (isEducator) {
    fetchDashboardData()
    
    // Refresh every 30 seconds
    const interval = setInterval(() => {
      fetchDashboardData()
    }, 30000)
    
    return () => clearInterval(interval)
  }
}, [isEducator])

API Reference

Get Dashboard Data

GET /api/educator/dashboard

Headers:
  Authorization: Bearer <jwt_token>

Get Quiz Insights

GET /api/quiz/educator-insights

Headers:
  Authorization: Bearer <jwt_token>

Interpreting Your Metrics

Possible reasons:
  • Course pricing too high
  • Weak course description or thumbnail
  • Limited marketing/visibility
  • Competitive niche
Actions to take:
  • Review and improve course thumbnail
  • Add free preview lectures
  • Offer limited-time discount
  • Share on social media
If quiz insights show many “Needs Review” students:Possible reasons:
  • Content too advanced
  • Insufficient explanation
  • Missing prerequisites
  • Quiz difficulty mismatch
Actions to take:
  • Add supplementary materials
  • Create additional introductory lectures
  • Clarify prerequisites in description
  • Review quiz question difficulty
If most students are “Mastered”:Good signs:
  • Clear explanations
  • Well-paced content
  • Good course structure
Next steps:
  • Consider advanced follow-up course
  • Add bonus/challenge content
  • Request student testimonials

Best Practices

Check Daily

Monitor your dashboard regularly to track growth trends and student engagement patterns

Analyze Patterns

Look for enrollment spikes after marketing efforts or course updates

Act on Insights

Use quiz performance data to improve specific chapters or add supplementary content

Celebrate Milestones

Acknowledge achievements like first 100 students or ₹1,00,000 earnings

Next Steps