Back to Course |
Creating a Quiz System with Laravel 10 + Livewire 3: Step-by-Step

Showing Quiz/Test Result

After saving the quiz results, now we can show user how did they do in the quiz, also adding the leaderboard.

leaderbord

First, we need a controller and a route for that.

php artisan make:controller ResultController

routes/web.php:

Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('quiz/{quiz}/{slug?}', [HomeController::class, 'show'])->name('quiz.show');
Route::get('results/{test}', [ResultController::class, 'show'])->name('results.show');
// ...

And now after creating the test we can redirect to this page.

app/Livewire/Front/Quizzes/Show.php:

class Show extends Component
{
// ...
public function submit(): Redirector|RedirectResponse
{
// ...
 
return to_route('home');
return to_route('results.show', $test);
}
// ...
}

So first, in the controller let's get the test.

app/Http/Controllers/ResultController.php:

class ResultController extends Controller
{
public function show(Test $test)
{
$total_questions = $test->quiz->questions->count();
 
return view('front.results.show', compact('test'));
}
}

And let's show the result for the user, but the user row will be shown only for the admin user.

resources/views/front/results/show.blade.php:

<x-app-layout>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<h6 class="text-xl font-bold">My Results</h6>
 
<table class="mt-4 table w-full table-view">
<tbody class="bg-white">
@if(auth()->user()?->is_admin)
<tr class="w-28">
<th class="border border-solid bg-slate-100 px-6 py-3 text-left text-sm font-semibold uppercase text-slate-600">User</th>
<td class="border border-solid px-6 py-3">{{ $test->user->name ?? '' }} ({{ $test->user->email ?? '' }})</td>
</tr>
@endif
<tr class="w-28">
<th class="border border-solid bg-slate-100 px-6 py-3 text-left text-sm font-semibold uppercase text-slate-600">Date</th>
<td class="border border-solid px-6 py-3">{{ $test->created_at ?? '' }}</td>
</tr>
<tr class="w-28">
<th class="border border-solid bg-slate-100 px-6 py-3 text-left text-sm font-semibold uppercase text-slate-600">Result</th>
<td class="border border-solid px-6 py-3">
{{ $test->result }} / {{ $total_questions }}
@if($test->time_spent)
(time: {{ intval($test->time_spent / 60) }}:{{ gmdate('s', $test->time_spent) }}
minutes)
@endif
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</x-app-layout>

For now, we see results similar to those below:

my results card

Next, let's show questions and answers.

app/Http/Controllers/ResultController.php:

class ResultController extends Controller
{
public function show(Test $test)
{
$total_questions = $test->quiz->questions->count();
 
$results = TestAnswer::where('test_id', $test->id)
->with('question.questionOptions')
->get();
 
return view('front.results.show', compact('test', 'results'));
}
}

resources/views/front/results/show.blade.php:

<x-app-layout>
// ..
 
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
@foreach($results as $result)
<table class="table table-view w-full my-4 bg-white">
<tbody class="bg-white">
<tr class="bg-slate-100">
<td class="w-1/2">Question #{{ $loop->iteration }}</td>
<td>{!! nl2br($result->question->question_text) !!}</td>
</tr>
<tr>
<td>Options</td>
<td>
@foreach($result->question->questionOptions as $option)
<li @class([
'underline' => $result->option_id == $option->id,
'font-bold' => $option->correct == 1,
])>
{{ $option->option }}
@if ($option->correct == 1) <span class="italic">(correct answer)</span> @endif
@if ($result->option_id == $option->id) <span class="italic">(your answer)</span> @endif
</li>
@endforeach
@if(is_null($result->option_id))
<span class="font-bold italic">Question unanswered.</span>
@endif
</td>
</tr>
@if($result->question->answer_explanation || $result->question->more_info_link)
<tr>
<td>Answer Explanation</td>
<td>
{!! $result->question->answer_explanation !!}
@if ($result->question->more_info_link)
<div class="mt-4">
Read more:
<a href="{{ $result->question->more_info_link }}" class="hover:underline" target="_blank">
{{ $result->question->more_info_link }}
</a>
</div>
@endif
</td>
</tr>
@endif
</tbody>
</table>
 
@if(!$loop->last)
<hr class="my-4 md:min-w-full">
@endif
@endforeach
</div>
</div>
</div>
</x-app-layout>

The result now we have is similar to the one below:

results

Next, let's add the last "card" where we will show the leaderboard. But it will be only shown if the quiz isn't public.

app/Http/Controllers/ResultController.php:

<?php
 
namespace App\Http\Controllers;
 
use App\Models\Test;
use App\Models\User;
use App\Models\TestAnswer;
 
class ResultController extends Controller
{
public function show(Test $test)
{
$test->load('user', 'quiz');
$total_questions = $test->quiz->questions->count();
$users = null;
 
$results = TestAnswer::where('test_id', $test->id)
->with('question.questionOptions')
->get();
 
if ($test->quiz->public == 0) {
$users = User::select('users.id', 'users.name', \DB::raw('sum(tests.result) as correct'), \DB::raw('sum(tests.time_spent) as time_spent'))
->join('tests', 'users.id', '=', 'tests.user_id')
->where('tests.quiz_id', $test->quiz_id)
->whereNotNull('tests.time_spent')
->groupBy('users.id', 'users.name')
->orderBy('correct', 'desc')
->orderBy('time_spent')
->get();
}
 
return view('front.results.show', compact('test', 'results', 'total_questions', 'users'));
}
}

We'll add a leaderboard "card" between My Results and Questions and Answers.

resources/views/front/results/show.blade.php:

<x-app-layout>
// ...
@if($users)
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 pb-12">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<h6 class="text-xl font-bold">Leaderboard</h6>
 
<table class="table mt-4 w-full table-view">
<thead>
<tr>
<th></th>
<th>Username</th>
<th>Correct answers</th>
</tr>
</thead>
<tbody class="bg-white">
@forelse($users as $user)
<tr @class(['bg-slate-100' => auth()->user() && auth()->user()->name == $user->name])>
<td class="w-9">{{ $loop->iteration }}</td>
<td class="w-1/2">{{ $user->name }}</td>
<td>{{ $user->correct }} / {{ $total_questions }}
(time: {{ intval($user->time_spent / 60) }}:{{ gmdate('s', $user->time_spent) }} minutes)</td>
</tr>
@empty
<tr>
<td colspan="3">No results</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
@endif
// ...
</x-app-layout>

If the quiz isn't public you should see a similar result:

leaderbord