Billie/app/Http/Controllers/NodeJS/Teacher/RankController.php

249 lines
8.2 KiB
PHP

<?php
namespace App\Http\Controllers\NodeJS\Teacher;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class RankController extends Controller
{
/**
* Display the ranking page
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('nodejs.rank-teacher.index');
}
/**
* Get the ranking data for a specific project
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function getRankingByProject(Request $request)
{
$projectId = $request->input('project_id');
if (!$projectId) {
return response()->json([
'status' => 'error',
'message' => 'Project ID is required'
], 400);
}
// Get all submissions for this project
$submissions = DB::table('submissions')
->join('users', 'submissions.user_id', '=', 'users.id')
->where('submissions.project_id', $projectId)
->select(
'submissions.id',
'submissions.user_id',
'users.name as user_name',
'submissions.attempts',
'submissions.results',
'submissions.updated_at'
)
->get();
// Process the submissions to calculate scores
$rankings = [];
$processedUsers = [];
foreach ($submissions as $submission) {
$userId = $submission->user_id;
// Parse the results JSON
$resultsData = json_decode($submission->results, true);
if (!$resultsData) {
continue; // Skip invalid results
}
// Calculate total tests and passed tests
$totalTests = 0;
$passedTests = 0;
foreach ($resultsData as $testSuite) {
if (isset($testSuite['testResults'])) {
foreach ($testSuite['testResults'] as $result) {
if (isset($result['totalTests']) && isset($result['passedTests'])) {
$totalTests += $result['totalTests'];
$passedTests += $result['passedTests'];
}
}
}
}
// Calculate percentage score and round to nearest integer
$score = $totalTests > 0 ? ($passedTests / $totalTests) * 100 : 0;
$score = round($score, 0); // Round to nearest integer
// If we already processed this user, only keep the higher score
// If scores are equal, keep the one with fewer attempts
if (isset($processedUsers[$userId])) {
$shouldReplace = false;
if ($score > $processedUsers[$userId]['score']) {
$shouldReplace = true;
} elseif ($score == $processedUsers[$userId]['score'] && $submission->attempts < $processedUsers[$userId]['attempts']) {
$shouldReplace = true;
}
if ($shouldReplace) {
$processedUsers[$userId] = [
'user_id' => $userId,
'user_name' => $submission->user_name,
'attempts' => $submission->attempts,
'score' => $score,
'total_tests' => $totalTests,
'passed_tests' => $passedTests,
'submission_date' => $submission->updated_at,
];
}
} else {
$processedUsers[$userId] = [
'user_id' => $userId,
'user_name' => $submission->user_name,
'attempts' => $submission->attempts,
'score' => $score,
'total_tests' => $totalTests,
'passed_tests' => $passedTests,
'submission_date' => $submission->updated_at,
];
}
}
// Convert to array and sort by score (descending), then by attempts (ascending), then by submission date (ascending)
$rankings = array_values($processedUsers);
usort($rankings, function($a, $b) {
// First priority: score (higher is better)
if ($a['score'] != $b['score']) {
return $b['score'] - $a['score'];
}
// Second priority: attempts (fewer is better)
if ($a['attempts'] != $b['attempts']) {
return $a['attempts'] - $b['attempts'];
}
// Third priority: submission date (earlier is better)
return strtotime($a['submission_date']) - strtotime($b['submission_date']);
});
// Add rank information
$rank = 1;
foreach ($rankings as $key => $value) {
$rankings[$key]['rank'] = $rank++;
}
return response()->json([
'status' => 'success',
'data' => [
'rankings' => $rankings
]
]);
}
/**
* Get all projects for the ranking dropdown
*
* @return \Illuminate\Http\JsonResponse
*/
public function getProjects()
{
$projects = DB::table('projects')
->select('id', 'title') // Alias 'title' as 'name' to match frontend expectations
->orderBy('title')
->get();
return response()->json([
'status' => 'success',
'data' => [
'projects' => $projects
]
]);
}
/**
* Export ranking data to CSV
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function exportRanking(Request $request)
{
$projectId = $request->input('project_id');
if (!$projectId) {
return response()->json([
'status' => 'error',
'message' => 'Project ID is required'
], 400);
}
// Get project name
$project = DB::table('projects')
->where('id', $projectId)
->first();
if (!$project) {
return response()->json([
'status' => 'error',
'message' => 'Project not found'
], 404);
}
// Get rankings data by calling the existing method
$rankingResponse = $this->getRankingByProject($request);
$content = json_decode($rankingResponse->getContent(), true);
if (!isset($content['data']['rankings'])) {
return response()->json([
'status' => 'error',
'message' => 'No ranking data available'
], 404);
}
$rankings = $content['data']['rankings'];
$filename = 'ranking_' . \Illuminate\Support\Str::slug($project->title) . '_' . date('Y-m-d') . '.csv';
// Create CSV headers
$headers = [
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=$filename",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0"
];
// Create the CSV file
$callback = function() use ($rankings) {
$file = fopen('php://output', 'w');
// Add CSV header row
fputcsv($file, ['Rank', 'Name', 'Attempts', 'Score (%)', 'Passed Tests', 'Total Tests', 'Submission Date']);
// Add data rows
foreach ($rankings as $ranking) {
fputcsv($file, [
$ranking['rank'],
$ranking['user_name'],
$ranking['attempts'],
$ranking['score'],
$ranking['passed_tests'],
$ranking['total_tests'],
$ranking['submission_date']
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}