@extends('backend.admin_backend.admin_dashboard') @section('admin') @php /* |-------------------------------------------------------------------------- | AGM SMARTSCHOOL - ACADEMIC PERFORMANCE ANALYTICS DASHBOARD |-------------------------------------------------------------------------- | | PURPOSE: | | This dashboard gives the school administrator a complete picture | of academic performance. | | The administrator can select: | | 1. Academic Session | 2. Term | 3. Class | | The dashboard then recalculates the academic analysis based on | those selections. | |-------------------------------------------------------------------------- | IMPORTANT RESULT CALCULATION |-------------------------------------------------------------------------- | | We are using the "total" column from the results table. | | Example: | | Mathematics = 72 | English = 65 | Biology = 80 | | Student average: | | (72 + 65 + 80) / 3 | | This gives the student's average performance. | |-------------------------------------------------------------------------- */ /* |-------------------------------------------------------------------------- | IMPORT MODELS |-------------------------------------------------------------------------- */ use App\Models\Student; use App\Models\StudentClasses; use App\Models\Teacher; use App\Models\Subject; use App\Models\Result; use App\Models\CbtTest; use App\Models\CbtAttempt; use Illuminate\Support\Facades\DB; /* |-------------------------------------------------------------------------- | BASIC SCHOOL STATISTICS |-------------------------------------------------------------------------- | | These figures are not affected by the analysis filter. | | They represent the entire school. | |-------------------------------------------------------------------------- */ $totalstudent = Student::count(); $totalsubject = Subject::count(); $totalclass = StudentClasses::count(); $totalteachers = Teacher::count(); /* |-------------------------------------------------------------------------- | LOAD ALL ACADEMIC SESSIONS |-------------------------------------------------------------------------- | | We load sessions so the administrator can select which academic | session should be analysed. | |-------------------------------------------------------------------------- */ $academicSessions = DB::table('academic_sessions') ->orderByDesc('name') ->get(); /* |-------------------------------------------------------------------------- | LOAD ALL TERMS |-------------------------------------------------------------------------- | | We load all available terms. | |-------------------------------------------------------------------------- */ $terms = DB::table('terms') ->orderBy('id') ->get(); /* |-------------------------------------------------------------------------- | LOAD ALL CLASSES |-------------------------------------------------------------------------- | | The administrator can analyse: | | - All classes | - One particular class | |-------------------------------------------------------------------------- */ $allClasses = StudentClasses::orderBy('class_name') ->get(); /* |-------------------------------------------------------------------------- | CURRENT SESSION |-------------------------------------------------------------------------- | | We use the current academic session as the default selection. | |-------------------------------------------------------------------------- */ $currentSessionRecord = DB::table('academic_sessions') ->where('is_current', 1) ->first(); /* |-------------------------------------------------------------------------- | CURRENT TERM |-------------------------------------------------------------------------- | | We use the current term as the default selection. | |-------------------------------------------------------------------------- */ $currentTermRecord = DB::table('terms') ->where('is_current', 1) ->first(); /* |-------------------------------------------------------------------------- | GET FILTER VALUES |-------------------------------------------------------------------------- | | request()->get() allows the dashboard to remember the selected | filter after the page reloads. | | Example: | | /admin/dashboard?session=2025/2026&term=First Term&class_id=5 | |-------------------------------------------------------------------------- */ $selectedSession = request()->get( 'session', $currentSessionRecord->name ?? '' ); $selectedTerm = request()->get( 'term', $currentTermRecord->name ?? '' ); /* |-------------------------------------------------------------------------- | CLASS FILTER |-------------------------------------------------------------------------- | | "all" means analyse the entire school. | | Otherwise, the administrator can select a specific class. | |-------------------------------------------------------------------------- */ $selectedClass = request()->get( 'class_id', 'all' ); /* |-------------------------------------------------------------------------- | DETERMINE SELECTED CLASS NAME |-------------------------------------------------------------------------- */ $selectedClassName = 'All Classes'; if ($selectedClass !== 'all') { $selectedClassRecord = StudentClasses::find( $selectedClass ); if ($selectedClassRecord) { $selectedClassName = $selectedClassRecord->class_name; } } /* |-------------------------------------------------------------------------- | DISPLAY PERIOD |-------------------------------------------------------------------------- | | This is displayed at the top of the dashboard so the admin always | knows which academic period is being analysed. | |-------------------------------------------------------------------------- */ $analysisPeriod = $selectedTerm . ' - ' . $selectedSession; /* |-------------------------------------------------------------------------- | RESULT QUERY |-------------------------------------------------------------------------- | | This is the most important query in the dashboard. | | Every academic analysis will start from this query. | | We filter by: | | TERM | SESSION | | Then, if a specific class was selected, we also filter by: | | STUDENT CLASS | |-------------------------------------------------------------------------- */ $resultQuery = Result::query() ->where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ); /* |-------------------------------------------------------------------------- | APPLY CLASS FILTER |-------------------------------------------------------------------------- */ if ($selectedClass !== 'all') { $resultQuery->where( 'student_classes_id', $selectedClass ); } /* |-------------------------------------------------------------------------- | CLONE RESULT QUERY |-------------------------------------------------------------------------- | | We will use the same base query for many different calculations. | | clone() prevents one calculation from changing another calculation. | |-------------------------------------------------------------------------- */ /* |-------------------------------------------------------------------------- | TOTAL RESULT RECORDS |-------------------------------------------------------------------------- | | Example: | | 30 students | 8 subjects | | Total result records: | | 30 x 8 = 240 | |-------------------------------------------------------------------------- */ $totalResultRecords = (clone $resultQuery) ->count(); /* |-------------------------------------------------------------------------- | STUDENTS WITH RESULTS |-------------------------------------------------------------------------- | | distinct student_id ensures that a student is counted only once, | even though the student may have many subjects. | |-------------------------------------------------------------------------- */ $studentsWithResults = (clone $resultQuery) ->distinct('student_id') ->count('student_id'); /* |-------------------------------------------------------------------------- | OVERALL AVERAGE |-------------------------------------------------------------------------- | | We calculate the average of all result totals belonging to the | selected term, session and optional class. | |-------------------------------------------------------------------------- */ $overallAverage = (clone $resultQuery) ->whereNotNull('total') ->avg('total'); $overallAverage = round( $overallAverage ?? 0, 2 ); /* |-------------------------------------------------------------------------- | OVERALL PERCENTAGE |-------------------------------------------------------------------------- | | Your result total is already stored as a percentage-style score | out of 100. | | Therefore: | | Average 68.50 = 68.50% | |-------------------------------------------------------------------------- */ $overallPercentage = $overallAverage; /* |-------------------------------------------------------------------------- | PASS MARK |-------------------------------------------------------------------------- | | We are using 40 as the default pass mark based on the existing | dashboard logic. | | You can later move this to school_settings so the administrator | can configure it. | |-------------------------------------------------------------------------- */ $passMark = 40; /* |-------------------------------------------------------------------------- | STUDENT AVERAGES |-------------------------------------------------------------------------- | | Here we calculate EACH student's average. | | Example: | | Student A: | | Mathematics = 70 | English = 80 | Biology = 60 | | Average: | | (70 + 80 + 60) / 3 = 70 | |-------------------------------------------------------------------------- */ $studentAverages = (clone $resultQuery) ->select( 'student_id' ) ->selectRaw( 'AVG(total) as average_score' ) ->selectRaw( 'COUNT(*) as subject_count' ) ->groupBy( 'student_id' ) ->get(); /* |-------------------------------------------------------------------------- | ABOVE AND BELOW SCHOOL/CLASS AVERAGE |-------------------------------------------------------------------------- | | We compare each student's average with the calculated overall | average of the current analysis. | |-------------------------------------------------------------------------- */ $aboveAverageStudents = 0; $belowAverageStudents = 0; foreach ($studentAverages as $studentAverage) { if ( $studentAverage->average_score >= $overallAverage ) { $aboveAverageStudents++; } else { $belowAverageStudents++; } } /* |-------------------------------------------------------------------------- | PASSING AND FAILING STUDENTS |-------------------------------------------------------------------------- | | Student average >= 40: | | PASS | | Student average < 40: | | BELOW PASS MARK | |-------------------------------------------------------------------------- */ $passingStudents = 0; $failingStudents = 0; foreach ($studentAverages as $studentAverage) { if ( $studentAverage->average_score >= $passMark ) { $passingStudents++; } else { $failingStudents++; } } /* |-------------------------------------------------------------------------- | PASS PERCENTAGE |-------------------------------------------------------------------------- */ $passPercentage = $studentsWithResults > 0 ? round( ( $passingStudents / $studentsWithResults ) * 100, 2 ) : 0; /* |-------------------------------------------------------------------------- | FAIL PERCENTAGE |-------------------------------------------------------------------------- */ $failPercentage = $studentsWithResults > 0 ? round( ( $failingStudents / $studentsWithResults ) * 100, 2 ) : 0; /* |-------------------------------------------------------------------------- | ABOVE AVERAGE PERCENTAGE |-------------------------------------------------------------------------- */ $aboveAveragePercentage = $studentsWithResults > 0 ? round( ( $aboveAverageStudents / $studentsWithResults ) * 100, 2 ) : 0; /* |-------------------------------------------------------------------------- | BELOW AVERAGE PERCENTAGE |-------------------------------------------------------------------------- */ $belowAveragePercentage = $studentsWithResults > 0 ? round( ( $belowAverageStudents / $studentsWithResults ) * 100, 2 ) : 0; /* |-------------------------------------------------------------------------- | CLASS PERFORMANCE |-------------------------------------------------------------------------- | | If "All Classes" is selected, we analyse every class. | | If one class is selected, only that class is analysed. | |-------------------------------------------------------------------------- */ $classPerformance = []; $classesForAnalysis = $selectedClass === 'all' ? $allClasses : $allClasses->where( 'id', $selectedClass ); foreach ( $classesForAnalysis as $class ) { /* |-------------------------------------------------------------------------- | CLASS RESULT QUERY |-------------------------------------------------------------------------- */ $classResultQuery = Result::where( 'student_classes_id', $class->id ) ->where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ); /* |-------------------------------------------------------------------------- | CLASS AVERAGE |-------------------------------------------------------------------------- */ $classAverage = $classResultQuery->avg('total'); /* |-------------------------------------------------------------------------- | STUDENTS WITH RESULTS IN THIS CLASS |-------------------------------------------------------------------------- */ $classStudentCount = $classResultQuery ->distinct('student_id') ->count('student_id'); /* |-------------------------------------------------------------------------- | RESULT RECORD COUNT |-------------------------------------------------------------------------- */ $classResultCount = $classResultQuery->count(); /* |-------------------------------------------------------------------------- | SAVE CLASS ANALYSIS |-------------------------------------------------------------------------- */ $classPerformance[] = [ 'id' => $class->id, 'name' => $class->class_name, 'average' => round( $classAverage ?? 0, 2 ), 'students' => $classStudentCount, 'results' => $classResultCount, ]; } /* |-------------------------------------------------------------------------- | CLASS CHART DATA |-------------------------------------------------------------------------- */ $classChartLabels = []; $classChartAverages = []; foreach ( $classPerformance as $classData ) { $classChartLabels[] = $classData['name']; $classChartAverages[] = $classData['average']; } /* |-------------------------------------------------------------------------- | CLASS ABOVE / BELOW AVERAGE |-------------------------------------------------------------------------- | | This tells the administrator how many students in each class are | above or below the current analysis average. | |-------------------------------------------------------------------------- */ $classAboveBelow = []; foreach ( $classesForAnalysis as $class ) { $studentClassResults = Result::where( 'student_classes_id', $class->id ) ->where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ) ->select( 'student_id' ) ->selectRaw( 'AVG(total) as average_score' ) ->groupBy( 'student_id' ) ->get(); $above = 0; $below = 0; foreach ( $studentClassResults as $studentResult ) { if ( $studentResult->average_score >= $overallAverage ) { $above++; } else { $below++; } } $classAboveBelow[] = [ 'class' => $class->class_name, 'above' => $above, 'below' => $below, ]; } /* |-------------------------------------------------------------------------- | SUBJECT PERFORMANCE |-------------------------------------------------------------------------- | | We calculate the average result for every subject. | |-------------------------------------------------------------------------- */ $subjectPerformance = []; $subjects = Subject::orderBy( 'subject_name' )->get(); foreach ( $subjects as $subject ) { /* |-------------------------------------------------------------------------- | SUBJECT QUERY |-------------------------------------------------------------------------- */ $subjectResultQuery = (clone $resultQuery) ->where( 'subject_id', $subject->id ); /* |-------------------------------------------------------------------------- | SUBJECT AVERAGE |-------------------------------------------------------------------------- */ $subjectAverage = $subjectResultQuery->avg( 'total' ); /* |-------------------------------------------------------------------------- | SUBJECT STUDENT COUNT |-------------------------------------------------------------------------- */ $subjectStudentCount = $subjectResultQuery ->distinct('student_id') ->count('student_id'); /* |-------------------------------------------------------------------------- | SAVE SUBJECT ANALYSIS |-------------------------------------------------------------------------- */ $subjectPerformance[] = [ 'id' => $subject->id, 'name' => $subject->subject_name, 'average' => round( $subjectAverage ?? 0, 2 ), 'students' => $subjectStudentCount, ]; } /* |-------------------------------------------------------------------------- | SUBJECT CHART DATA |-------------------------------------------------------------------------- */ $subjectChartLabels = []; $subjectChartAverages = []; foreach ( $subjectPerformance as $subjectData ) { $subjectChartLabels[] = $subjectData['name']; $subjectChartAverages[] = $subjectData['average']; } /* |-------------------------------------------------------------------------- | GRADE DISTRIBUTION |-------------------------------------------------------------------------- | | We count how many result records belong to each grade. | | Example: | | A = 50 | B = 80 | C = 100 | D = 40 | |-------------------------------------------------------------------------- */ $gradeDistribution = (clone $resultQuery) ->select('grade') ->selectRaw( 'COUNT(*) as total' ) ->groupBy('grade') ->orderBy('grade') ->get(); $gradeLabels = []; $gradeCounts = []; foreach ( $gradeDistribution as $grade ) { $gradeLabels[] = $grade->grade ?: 'N/A'; $gradeCounts[] = $grade->total; } /* |-------------------------------------------------------------------------- | TOP STUDENTS |-------------------------------------------------------------------------- | | We calculate the average of each student and display the students | with the highest calculated averages. | | This is not changing their result. | | It is simply an analysis of the existing result records. | |-------------------------------------------------------------------------- */ $topStudents = (clone $resultQuery) ->select( 'student_id' ) ->selectRaw( 'AVG(total) as average_score' ) ->selectRaw( 'COUNT(*) as subject_count' ) ->groupBy( 'student_id' ) ->orderByDesc( 'average_score' ) ->limit(10) ->get(); $topStudentData = []; foreach ( $topStudents as $studentResult ) { $student = Student::with('user') ->find( $studentResult->student_id ); if (!$student) { continue; } /* |-------------------------------------------------------------------------- | STUDENT NAME |-------------------------------------------------------------------------- */ $studentName = $student->user ? $student->user->name : 'Student #' . $student->id; /* |-------------------------------------------------------------------------- | CLASS NAME |-------------------------------------------------------------------------- */ $studentClass = StudentClasses::find( $student->student_classes_id ); $topStudentData[] = [ 'name' => $studentName, 'class' => $studentClass ? $studentClass->class_name : 'N/A', 'average' => round( $studentResult->average_score, 2 ), 'subjects' => $studentResult->subject_count, ]; } /* |-------------------------------------------------------------------------- | STUDENTS BELOW AVERAGE |-------------------------------------------------------------------------- | | These are useful for identifying students that may need additional | academic attention. | |-------------------------------------------------------------------------- */ $studentsBelowAverage = []; $belowAverageQuery = (clone $resultQuery) ->select( 'student_id' ) ->selectRaw( 'AVG(total) as average_score' ) ->selectRaw( 'COUNT(*) as subject_count' ) ->groupBy( 'student_id' ) ->having( DB::raw('AVG(total)'), '<', $overallAverage ) ->orderBy( 'average_score' ) ->limit(20) ->get(); foreach ( $belowAverageQuery as $studentResult ) { $student = Student::with('user') ->find( $studentResult->student_id ); if (!$student) { continue; } $studentName = $student->user ? $student->user->name : 'Student #' . $student->id; $studentClass = StudentClasses::find( $student->student_classes_id ); $studentsBelowAverage[] = [ 'name' => $studentName, 'class' => $studentClass ? $studentClass->class_name : 'N/A', 'average' => round( $studentResult->average_score, 2 ), ]; } /* |-------------------------------------------------------------------------- | STUDENTS ABOVE AVERAGE |-------------------------------------------------------------------------- */ $studentsAboveAverage = []; $aboveAverageQuery = (clone $resultQuery) ->select( 'student_id' ) ->selectRaw( 'AVG(total) as average_score' ) ->selectRaw( 'COUNT(*) as subject_count' ) ->groupBy( 'student_id' ) ->having( DB::raw('AVG(total)'), '>=', $overallAverage ) ->orderByDesc( 'average_score' ) ->limit(20) ->get(); foreach ( $aboveAverageQuery as $studentResult ) { $student = Student::with('user') ->find( $studentResult->student_id ); if (!$student) { continue; } $studentName = $student->user ? $student->user->name : 'Student #' . $student->id; $studentClass = StudentClasses::find( $student->student_classes_id ); $studentsAboveAverage[] = [ 'name' => $studentName, 'class' => $studentClass ? $studentClass->class_name : 'N/A', 'average' => round( $studentResult->average_score, 2 ), ]; } /* |-------------------------------------------------------------------------- | CA / EXAM PERFORMANCE |-------------------------------------------------------------------------- | | This is one of the most useful additions. | | Instead of looking only at the final total, we can see how students | performed in: | | CA1 | CA2 | CA3 | EXAM | | This can help the school identify whether students are performing | consistently during continuous assessment or only during exams. | |-------------------------------------------------------------------------- */ $componentPerformance = [ 'CA1' => round( (clone $resultQuery)->avg('ca1') ?? 0, 2 ), 'CA2' => round( (clone $resultQuery)->avg('ca2') ?? 0, 2 ), 'CA3' => round( (clone $resultQuery)->avg('ca3') ?? 0, 2 ), 'Exam' => round( (clone $resultQuery)->avg('exam') ?? 0, 2 ), ]; /* |-------------------------------------------------------------------------- | PUBLICATION ANALYSIS |-------------------------------------------------------------------------- | | This connects the dashboard with the result publication feature | we built earlier. | |-------------------------------------------------------------------------- */ $totalPublishedResults = (clone $resultQuery) ->where( 'publish', 1 ) ->count(); $totalUnpublishedResults = max( $totalResultRecords - $totalPublishedResults, 0 ); $publicationPercentage = $totalResultRecords > 0 ? round( ( $totalPublishedResults / $totalResultRecords ) * 100, 2 ) : 0; /* |-------------------------------------------------------------------------- | TERM-TO-TERM PERFORMANCE |-------------------------------------------------------------------------- | | This is useful when the admin selects a particular session. | | We calculate the average for: | | First Term | Second Term | Third Term | | It allows the dashboard to show academic movement across the year. | |-------------------------------------------------------------------------- */ $termPerformance = []; foreach ( $terms as $term ) { $termAverageQuery = Result::where( 'session', $selectedSession ) ->where( 'term', $term->name ); /* |-------------------------------------------------------------------------- | APPLY CLASS FILTER TO TERM ANALYSIS |-------------------------------------------------------------------------- */ if ( $selectedClass !== 'all' ) { $termAverageQuery->where( 'student_classes_id', $selectedClass ); } $termAverage = $termAverageQuery->avg( 'total' ); $termPerformance[] = [ 'term' => $term->name, 'average' => round( $termAverage ?? 0, 2 ), ]; } /* |-------------------------------------------------------------------------- | TERM CHART DATA |-------------------------------------------------------------------------- */ $termChartLabels = []; $termChartAverages = []; foreach ( $termPerformance as $termData ) { $termChartLabels[] = $termData['term']; $termChartAverages[] = $termData['average']; } /* |-------------------------------------------------------------------------- | CBT ANALYTICS |-------------------------------------------------------------------------- | | We keep your existing CBT dashboard functionality. | |-------------------------------------------------------------------------- */ $totalTests = CbtTest::where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ) ->count(); $totalAttempts = CbtAttempt::whereHas( 'test', function ($q) use ( $selectedTerm, $selectedSession ) { $q->where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ); } ) ->count(); $completedAttempts = CbtAttempt::where( 'status', 'completed' ) ->whereHas( 'test', function ($q) use ( $selectedTerm, $selectedSession ) { $q->where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ); } ) ->count(); $inProgressAttempts = CbtAttempt::where( 'status', 'in_progress' ) ->whereHas( 'test', function ($q) use ( $selectedTerm, $selectedSession ) { $q->where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ); } ) ->count(); $passCount = CbtAttempt::where( 'status', 'completed' ) ->whereHas( 'test', function ($q) use ( $selectedTerm, $selectedSession ) { $q->where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ); } ) ->where( 'score', '>=', 40 ) ->count(); $failCount = CbtAttempt::where( 'status', 'completed' ) ->whereHas( 'test', function ($q) use ( $selectedTerm, $selectedSession ) { $q->where( 'term', $selectedTerm ) ->where( 'session', $selectedSession ); } ) ->where( 'score', '<', 40 ) ->count(); /* |-------------------------------------------------------------------------- | STUDENT GROWTH |-------------------------------------------------------------------------- | | This remains based on student registration date. | |-------------------------------------------------------------------------- */ $monthlyStudents = []; $months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; foreach ( $months as $index => $month ) { $monthlyStudents[] = Student::whereYear( 'created_at', date('Y') ) ->whereMonth( 'created_at', $index + 1 ) ->count(); } /* |-------------------------------------------------------------------------- | CLASS ABOVE / BELOW CHART DATA |-------------------------------------------------------------------------- */ $classAboveLabels = []; $classAboveCounts = []; $classBelowCounts = []; foreach ( $classAboveBelow as $classData ) { $classAboveLabels[] = $classData['class']; $classAboveCounts[] = $classData['above']; $classBelowCounts[] = $classData['below']; } @endphp
Academic analysis, performance monitoring and result statistics
Total Students
Total Subjects
Total Classes
Total Teachers
Student Pass Rate
Below Pass Mark
Result Publication
| # | Student | Class | Average |
|---|---|---|---|
| {{ $index + 1 }} | {{ $student['name'] }} | {{ $student['class'] }} | {{ number_format( $student['average'], 2 ) }}% |
| No student result available. | |||
| # | Student | Class | Average |
|---|---|---|---|
| {{ $index + 1 }} | {{ $student['name'] }} | {{ $student['class'] }} | {{ number_format( $student['average'], 2 ) }}% |
| No student below average. | |||
| # | Student | Class | Average | Subjects |
|---|---|---|---|---|
| {{ $index + 1 }} | {{ $student['name'] }} | {{ $student['class'] }} | {{ number_format( $student['average'], 2 ) }}% | {{ $student['subjects'] }} |
| No result available. | ||||
| # | Class | Students | Result Records | Average | Performance |
|---|---|---|---|---|---|
| {{ $index + 1 }} | {{ $classData['name'] }} | {{ $classData['students'] }} | {{ $classData['results'] }} | {{ number_format( $classData['average'], 2 ) }}% |
|
| No class result available. | |||||
| # | Subject | Students | Average |
|---|---|---|---|
| {{ $index + 1 }} | {{ $subjectData['name'] }} | {{ $subjectData['students'] }} | {{ number_format( $subjectData['average'], 2 ) }}% |
| No subject result available. | |||