> ## Documentation Index
> Fetch the complete documentation index at: https://skillrisedocs.pushkarverma.online/llms.txt
> Use this file to discover all available pages before exploring further.

# Analytics & Time Tracking

> Comprehensive learning analytics with time tracking, progress dashboards, and course breakdowns

## Overview

SkillRise tracks detailed learning analytics including time spent on pages, daily activity patterns, and per-course breakdowns. The system provides insights for both students and educators.

## Time Tracking Model

### TimeTracking Schema

```javascript server/models/TimeTracking.js theme={null}
import mongoose from 'mongoose'

const timeTrackingSchema = new mongoose.Schema(
  {
    userId: { type: String, required: true },
    page: { type: String, required: true },
    path: { type: String, required: true },
    duration: { type: Number, required: true }, // in seconds
    date: { type: Date, default: Date.now },
  },
  { timestamps: true }
)

const TimeTracking = mongoose.model('TimeTracking', timeTrackingSchema)
export default TimeTracking
```

<Note>
  Each tracking record captures a single page session with duration in seconds, allowing granular analysis of learning patterns.
</Note>

## Tracking Implementation

### Record Time Spent

Endpoint for frontend to report time spent on pages:

```javascript server/controllers/timeTrackingController.js theme={null}
import TimeTracking from '../models/TimeTracking.js'
import { z } from 'zod'

const TrackTimeBodySchema = z.object({
  page: z.string().min(1),
  path: z.string().min(1),
  duration: z.number().positive(),
})

export const trackTime = async (req, res) => {
  try {
    const userId = req.auth.userId

    const bodyResult = TrackTimeBodySchema.safeParse(req.body)
    if (!bodyResult.success) {
      return res.status(400).json({ 
        success: false, 
        message: 'Invalid tracking data' 
      })
    }
    const { page, path, duration } = bodyResult.data

    await TimeTracking.create({ 
      userId, 
      page, 
      path, 
      duration: Math.round(duration) 
    })
    
    res.json({ success: true })
  } catch (error) {
    console.error(error)
    res.status(500).json({ 
      success: false, 
      message: 'An unexpected error occurred' 
    })
  }
}
```

<Info>
  Duration is rounded to the nearest second to ensure consistent data storage.
</Info>

## Analytics Dashboard

### Comprehensive Analytics Endpoint

Generate multi-faceted analytics from tracking data:

```javascript server/controllers/timeTrackingController.js theme={null}
const EXCLUDED_PAGES = new Set([
  'Home', 
  'Browse Courses', 
  'Course Details', 
  'Analytics', 
  'Other'
])

export const getAnalytics = async (req, res) => {
  try {
    const userId = req.auth.userId
    const records = await TimeTracking.find({ userId })
      .sort({ date: -1 })

    const activeRecords = records.filter(
      (r) => !EXCLUDED_PAGES.has(r.page)
    )

    // Per-page aggregation
    const pageMap = {}
    activeRecords.forEach((r) => {
      if (!pageMap[r.page]) {
        pageMap[r.page] = { 
          page: r.page, 
          path: r.path, 
          totalDuration: 0, 
          visits: 0 
        }
      }
      pageMap[r.page].totalDuration += r.duration
      pageMap[r.page].visits += 1
    })
    const pageStats = Object.values(pageMap)
      .sort((a, b) => b.totalDuration - a.totalDuration)

    // Last 7 days daily breakdown
    const dailyMap = {}
    for (let i = 6; i >= 0; i--) {
      const d = new Date()
      d.setDate(d.getDate() - i)
      dailyMap[d.toISOString().split('T')[0]] = 0
    }
    activeRecords.forEach((r) => {
      const key = new Date(r.date).toISOString().split('T')[0]
      if (key in dailyMap) dailyMap[key] += r.duration
    })
    const dailyStats = Object.entries(dailyMap)
      .map(([date, duration]) => ({ date, duration }))

    const totalDuration = activeRecords.reduce(
      (sum, r) => sum + r.duration, 
      0
    )
    const totalSessions = activeRecords.length

    // Course breakdown logic continues...
    const courseBreakdown = buildCourseBreakdown(records)

    res.json({
      success: true,
      analytics: { 
        totalDuration, 
        totalSessions, 
        pageStats, 
        dailyStats, 
        courseBreakdown 
      },
    })
  } catch (error) {
    console.error(error)
    res.status(500).json({ 
      success: false, 
      message: 'An unexpected error occurred' 
    })
  }
}
```

<Steps>
  <Step title="Fetch Records">
    Retrieve all time tracking records for the user, sorted by most recent.
  </Step>

  <Step title="Filter Active Pages">
    Exclude navigation and metadata pages to focus on learning activity.
  </Step>

  <Step title="Per-Page Aggregation">
    Group records by page and calculate total duration and visit count.
  </Step>

  <Step title="Daily Breakdown">
    Generate last 7 days of activity for time-series visualization.
  </Step>

  <Step title="Course Analysis">
    Extract course-specific time tracking from URL patterns.
  </Step>
</Steps>

## Course Breakdown Analysis

### Extract Learning Time by Course

```javascript server/controllers/timeTrackingController.js theme={null}
const breakdownMap = {}

// Group learning time by courseId (from /player/:courseId)
records
  .filter((r) => r.path.startsWith('/player/'))
  .forEach((r) => {
    const courseId = r.path.split('/')[2]
    if (!courseId) return
    if (!breakdownMap[courseId])
      breakdownMap[courseId] = {
        courseId,
        learningDuration: 0,
        learningSessions: 0,
        chapters: {},
      }
    breakdownMap[courseId].learningDuration += r.duration
    breakdownMap[courseId].learningSessions += 1
  })

// Group quiz time by courseId + chapterId (from /quiz/:courseId/:chapterId)
records
  .filter((r) => r.path.startsWith('/quiz/'))
  .forEach((r) => {
    const parts = r.path.split('/')
    const courseId = parts[2]
    const chapterId = parts[3]
    if (!courseId || !chapterId) return
    if (!breakdownMap[courseId])
      breakdownMap[courseId] = {
        courseId,
        learningDuration: 0,
        learningSessions: 0,
        chapters: {},
      }
    if (!breakdownMap[courseId].chapters[chapterId]) {
      breakdownMap[courseId].chapters[chapterId] = {
        chapterId,
        quizDuration: 0,
        quizSessions: 0,
      }
    }
    breakdownMap[courseId].chapters[chapterId].quizDuration += r.duration
    breakdownMap[courseId].chapters[chapterId].quizSessions += 1
  })
```

### Enrich with Course Metadata

```javascript server/controllers/timeTrackingController.js theme={null}
// Fetch course titles + chapter titles in one query
const allCourseIds = Object.keys(breakdownMap)
const courses = await Course.find({ _id: { $in: allCourseIds } })
  .select('_id courseTitle courseThumbnail courseContent')

const courseDataMap = {}
courses.forEach((c) => {
  const chapterMap = {}
  c.courseContent.forEach((ch) => {
    chapterMap[ch.chapterId] = ch.chapterTitle
  })
  courseDataMap[c._id.toString()] = {
    title: c.courseTitle,
    thumbnail: c.courseThumbnail,
    chapterMap,
  }
})

const courseBreakdown = Object.values(breakdownMap)
  .map((entry) => {
    const info = courseDataMap[entry.courseId] || {}
    const chapters = Object.values(entry.chapters)
      .map((ch) => ({
        chapterId: ch.chapterId,
        chapterTitle: info.chapterMap?.[ch.chapterId] || 'Unknown Chapter',
        quizDuration: ch.quizDuration,
        quizSessions: ch.quizSessions,
      }))
      .sort((a, b) => b.quizDuration - a.quizDuration)

    const totalQuizDuration = chapters.reduce(
      (s, c) => s + c.quizDuration, 
      0
    )
    return {
      courseId: entry.courseId,
      courseTitle: info.title || 'Unknown Course',
      courseThumbnail: info.thumbnail || null,
      learningDuration: entry.learningDuration,
      learningSessions: entry.learningSessions,
      totalQuizDuration,
      chapters,
    }
  })
  .sort(
    (a, b) =>
      b.learningDuration + b.totalQuizDuration - 
      (a.learningDuration + a.totalQuizDuration)
  )
```

<Info>
  Course breakdown separates learning time (video player) from quiz time, providing granular insights into study patterns.
</Info>

## Analytics Data Structure

### Response Format

```json theme={null}
{
  "success": true,
  "analytics": {
    "totalDuration": 12650,
    "totalSessions": 47,
    "pageStats": [
      {
        "page": "Course Player",
        "path": "/player/course123",
        "totalDuration": 8340,
        "visits": 28
      },
      {
        "page": "Quiz",
        "path": "/quiz/course123/chapter1",
        "totalDuration": 2150,
        "visits": 12
      }
    ],
    "dailyStats": [
      { "date": "2026-02-26", "duration": 1820 },
      { "date": "2026-02-27", "duration": 2340 },
      { "date": "2026-02-28", "duration": 1950 },
      { "date": "2026-03-01", "duration": 2680 },
      { "date": "2026-03-02", "duration": 1740 },
      { "date": "2026-03-03", "duration": 1520 },
      { "date": "2026-03-04", "duration": 600 }
    ],
    "courseBreakdown": [
      {
        "courseId": "course123",
        "courseTitle": "Advanced JavaScript",
        "courseThumbnail": "https://...",
        "learningDuration": 8340,
        "learningSessions": 28,
        "totalQuizDuration": 2150,
        "chapters": [
          {
            "chapterId": "chapter1",
            "chapterTitle": "Closures and Scope",
            "quizDuration": 1200,
            "quizSessions": 7
          },
          {
            "chapterId": "chapter2",
            "chapterTitle": "Async Patterns",
            "quizDuration": 950,
            "quizSessions": 5
          }
        ]
      }
    ]
  }
}
```

## Educator Quiz Insights

### Quiz Performance Analytics

Educators can view aggregated quiz performance for their courses:

```javascript server/controllers/quizController.js theme={null}
export const getEducatorQuizInsights = async (req, res) => {
  try {
    const educatorId = req.auth.userId

    const courses = await Course.find({ educatorId })
    const courseIds = courses.map((c) => c._id.toString())

    const allResults = await QuizResult.find({ 
      courseId: { $in: courseIds } 
    })

    // Group by courseId + chapterId
    const statsMap = {}
    allResults.forEach((r) => {
      const key = `${r.courseId}__${r.chapterId}`
      if (!statsMap[key]) {
        statsMap[key] = {
          courseId: r.courseId,
          chapterId: r.chapterId,
          attempts: 0,
          totalPct: 0,
          needs_review: 0,
          on_track: 0,
          mastered: 0,
        }
      }
      statsMap[key].attempts++
      statsMap[key].totalPct += r.percentage
      statsMap[key][r.group]++
    })

    // Attach chapter + course titles
    const insights = Object.values(statsMap).map((entry) => {
      const course = courses.find(
        (c) => c._id.toString() === entry.courseId
      )
      const chapter = course?.courseContent.find(
        (ch) => ch.chapterId === entry.chapterId
      )
      return {
        ...entry,
        courseTitle: course?.courseTitle || 'Unknown',
        chapterTitle: chapter?.chapterTitle || 'Unknown',
        avgPct: Math.round(entry.totalPct / entry.attempts),
      }
    })

    // Sort by most attempts desc
    insights.sort((a, b) => b.attempts - a.attempts)

    res.json({ success: true, insights })
  } catch (error) {
    console.error(error)
    res.status(500).json({ 
      success: false, 
      message: 'An unexpected error occurred' 
    })
  }
}
```

<Steps>
  <Step title="Fetch Courses">
    Get all courses created by the educator.
  </Step>

  <Step title="Aggregate Results">
    Group quiz results by course and chapter combination.
  </Step>

  <Step title="Calculate Statistics">
    Compute average scores and performance group distributions.
  </Step>

  <Step title="Enrich Data">
    Add course and chapter titles for readable output.
  </Step>
</Steps>

## Analytics Visualizations

<CardGroup cols={2}>
  <Card title="Time Series" icon="chart-area">
    Daily activity over the last 7 days shows learning consistency patterns.
  </Card>

  <Card title="Course Distribution" icon="chart-pie">
    Per-course time breakdown reveals which courses students engage with most.
  </Card>

  <Card title="Session Analytics" icon="clock">
    Total sessions and average duration per page provide engagement metrics.
  </Card>

  <Card title="Quiz Performance" icon="clipboard-check">
    Performance group distribution helps educators identify challenging chapters.
  </Card>
</CardGroup>

## Key Metrics

<AccordionGroup>
  <Accordion title="Total Duration">
    Sum of all learning time (excluding navigation pages) measured in seconds.
  </Accordion>

  <Accordion title="Total Sessions">
    Count of individual page visits, indicating engagement frequency.
  </Accordion>

  <Accordion title="Learning vs Quiz Time">
    Separate tracking for video player sessions and quiz attempts.
  </Accordion>

  <Accordion title="Chapter-Level Granularity">
    Quiz time is tracked per chapter, enabling fine-grained difficulty analysis.
  </Accordion>

  <Accordion title="Performance Groups">
    Quiz results categorized as needs\_review, on\_track, or mastered.
  </Accordion>
</AccordionGroup>

## Frontend Integration

### Tracking Hook Example

```javascript client/src/hooks/useTimeTracking.js theme={null}
import { useEffect, useRef } from 'react'
import { useLocation } from 'react-router-dom'

export const useTimeTracking = (pageName) => {
  const location = useLocation()
  const startTime = useRef(Date.now())

  useEffect(() => {
    startTime.current = Date.now()

    return () => {
      const duration = Math.floor((Date.now() - startTime.current) / 1000)
      
      if (duration > 5) { // Only track sessions > 5 seconds
        fetch('/api/time-tracking/track', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            page: pageName,
            path: location.pathname,
            duration,
          }),
        })
      }
    }
  }, [location.pathname, pageName])
}
```

<Tip>
  Track time on component unmount to capture the complete session duration accurately.
</Tip>

## Best Practices

<CardGroup cols={2}>
  <Card title="Filter Noise" icon="filter">
    Exclude navigation and meta pages to focus analytics on learning content.
  </Card>

  <Card title="Minimum Threshold" icon="hourglass-start">
    Only track sessions longer than 5 seconds to avoid false data from quick navigations.
  </Card>

  <Card title="URL Pattern Matching" icon="route">
    Use URL patterns (/player/:id, /quiz/:id/:chapter) to extract contextual metadata.
  </Card>

  <Card title="Aggregate Efficiently" icon="bolt">
    Use in-memory aggregation for dashboard queries to minimize database load.
  </Card>
</CardGroup>

## Privacy Considerations

<Warning>
  All analytics are user-scoped and require authentication. Students can only view their own analytics, and educators can only view aggregate data for their courses.
</Warning>

## Next Steps

<CardGroup cols={3}>
  <Card title="AI Features" icon="brain" href="/features/ai-features">
    See how quiz performance feeds into AI recommendations
  </Card>

  <Card title="Course Management" icon="book" href="/features/course-management">
    Understand course structure tracked by analytics
  </Card>

  <Card title="Community" icon="users" href="/features/community">
    Engage with peers about learning strategies
  </Card>
</CardGroup>
