ajoute Quiz flash (chantier vrai cockpit, étape 2) : QCM et vrai/faux, lancé en direct, indépendant d'une séance de lab
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
45f8a69e26
commit
16d1293911
24 changed files with 1249 additions and 5 deletions
|
|
@ -9,9 +9,10 @@ use Illuminate\Support\Facades\Log;
|
|||
|
||||
/**
|
||||
* RGPD (CLAUDE.md, étape 14) : supprime les classes archivées depuis
|
||||
* 90 jours, avec en cascade leurs élèves, séances, sessions de lab, machines
|
||||
* et corrections. Le compte du prof est conservé. Le journal ne garde que
|
||||
* des identifiants et des nombres, jamais de nom d'élève.
|
||||
* 90 jours, avec en cascade leurs élèves, séances, sessions de lab, machines,
|
||||
* corrections et quiz (chantier « vrai cockpit », étape 2). Le compte du
|
||||
* prof est conservé. Le journal ne garde que des identifiants et des
|
||||
* nombres, jamais de nom d'élève.
|
||||
*
|
||||
* Les sauvegardes chiffrées contenant encore ces données expirent d'elles-
|
||||
* mêmes au plus 4 semaines plus tard (rotation, docs/runbooks/sauvegardes.md).
|
||||
|
|
|
|||
112
portail/app/Http/Controllers/QuizController.php
Normal file
112
portail/app/Http/Controllers/QuizController.php
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Classe;
|
||||
use App\Models\QuestionQuiz;
|
||||
use App\Models\Quiz;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* Chantier « vrai cockpit », étape 2 : Quiz flash. La banque de questions
|
||||
* (ce contrôleur) est distincte de son lancement en direct
|
||||
* (SessionQuizController) : un quiz se prépare à l'avance, se relance
|
||||
* autant de fois qu'on veut.
|
||||
*/
|
||||
class QuizController extends Controller
|
||||
{
|
||||
public function index(Classe $classe): View
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
|
||||
return view('quiz.index', ['classe' => $classe, 'quizzes' => $classe->quizzes()->withCount('questions')->get()]);
|
||||
}
|
||||
|
||||
public function store(Request $request, Classe $classe): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
$donnees = $request->validate(['titre' => ['required', 'string', 'max:150']]);
|
||||
|
||||
$quiz = $classe->quizzes()->create($donnees);
|
||||
|
||||
return redirect()->route('mes-classes.quiz.show', [$classe, $quiz]);
|
||||
}
|
||||
|
||||
public function show(Classe $classe, Quiz $quiz): View
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
$this->verifierAppartenance($classe, $quiz);
|
||||
|
||||
return view('quiz.show', ['classe' => $classe, 'quiz' => $quiz->load('questions.choix')]);
|
||||
}
|
||||
|
||||
public function destroy(Classe $classe, Quiz $quiz): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
$this->verifierAppartenance($classe, $quiz);
|
||||
$quiz->delete();
|
||||
|
||||
return redirect()->route('mes-classes.quiz.index', $classe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Une question à la fois (même esprit que « ajouter un élève ») : plus
|
||||
* simple à valider et à tester qu'un formulaire unique avec un nombre
|
||||
* de lignes variable.
|
||||
*/
|
||||
public function storeQuestion(Request $request, Classe $classe, Quiz $quiz): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
$this->verifierAppartenance($classe, $quiz);
|
||||
|
||||
$donnees = $request->validate([
|
||||
'enonce' => ['required', 'string', 'max:500'],
|
||||
'type' => ['required', 'in:'.QuestionQuiz::QCM.','.QuestionQuiz::VRAI_FAUX],
|
||||
'reponse_vrai_faux' => ['required_if:type,'.QuestionQuiz::VRAI_FAUX, 'boolean'],
|
||||
'choix' => ['required_if:type,'.QuestionQuiz::QCM, 'array', 'min:2', 'max:5'],
|
||||
'choix.*' => ['required_if:type,'.QuestionQuiz::QCM, 'string', 'max:200'],
|
||||
'bonne_reponse' => ['required_if:type,'.QuestionQuiz::QCM, 'integer'],
|
||||
]);
|
||||
|
||||
DB::transaction(function () use ($donnees, $quiz) {
|
||||
$question = $quiz->questions()->create([
|
||||
'enonce' => $donnees['enonce'],
|
||||
'type' => $donnees['type'],
|
||||
'ordre' => $quiz->questions()->count(),
|
||||
'reponse_vrai_faux' => $donnees['type'] === QuestionQuiz::VRAI_FAUX ? $donnees['reponse_vrai_faux'] : null,
|
||||
]);
|
||||
|
||||
if ($donnees['type'] === QuestionQuiz::QCM) {
|
||||
foreach ($donnees['choix'] as $i => $texte) {
|
||||
$question->choix()->create(['texte' => $texte, 'est_correct' => $i === (int) $donnees['bonne_reponse'], 'ordre' => $i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('mes-classes.quiz.show', [$classe, $quiz]);
|
||||
}
|
||||
|
||||
public function destroyQuestion(Classe $classe, Quiz $quiz, QuestionQuiz $question): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
$this->verifierAppartenance($classe, $quiz);
|
||||
abort_if($question->quiz_id !== $quiz->id, 404);
|
||||
$question->delete();
|
||||
|
||||
return redirect()->route('mes-classes.quiz.show', [$classe, $quiz]);
|
||||
}
|
||||
|
||||
private function verifierProprietaire(Classe $classe): void
|
||||
{
|
||||
abort_if($classe->user_id !== Auth::guard('web')->id(), 403);
|
||||
}
|
||||
|
||||
private function verifierAppartenance(Classe $classe, Quiz $quiz): void
|
||||
{
|
||||
abort_if($quiz->classe_id !== $classe->id, 404);
|
||||
}
|
||||
}
|
||||
102
portail/app/Http/Controllers/QuizEleveController.php
Normal file
102
portail/app/Http/Controllers/QuizEleveController.php
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\QuestionQuiz;
|
||||
use App\Models\ReponseQuiz;
|
||||
use App\Models\SessionQuiz;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
/**
|
||||
* Côté élève du quiz flash (chantier « vrai cockpit », étape 2) : pas de
|
||||
* code à saisir, pas d'invité — l'élève est déjà un compte réel de la
|
||||
* classe, le quiz apparaît directement sur son tableau de bord.
|
||||
*/
|
||||
class QuizEleveController extends Controller
|
||||
{
|
||||
public function etat(): JsonResponse
|
||||
{
|
||||
$eleve = Auth::guard('eleve')->user();
|
||||
|
||||
$session = SessionQuiz::query()
|
||||
->whereIn('statut', [SessionQuiz::EN_ATTENTE, SessionQuiz::EN_COURS])
|
||||
->whereHas('quiz', fn ($q) => $q->where('classe_id', $eleve->classe_id))
|
||||
->with('questionCourante.choix')
|
||||
->latest('demarree_a')
|
||||
->first();
|
||||
|
||||
if (! $session) {
|
||||
return response()->json(['session' => null]);
|
||||
}
|
||||
|
||||
$dejaRepondu = $session->question_quiz_courante_id
|
||||
? ReponseQuiz::where('session_quiz_id', $session->id)
|
||||
->where('question_quiz_id', $session->question_quiz_courante_id)
|
||||
->where('eleve_id', $eleve->id)
|
||||
->exists()
|
||||
: false;
|
||||
|
||||
$question = $session->questionCourante;
|
||||
|
||||
return response()->json([
|
||||
'session' => [
|
||||
'id' => $session->id,
|
||||
'statut' => $session->statut,
|
||||
'deja_repondu' => $dejaRepondu,
|
||||
'question' => $question ? [
|
||||
'id' => $question->id,
|
||||
'enonce' => $question->enonce,
|
||||
'type' => $question->type,
|
||||
'choix' => $question->type === QuestionQuiz::QCM
|
||||
? $question->choix->map(fn ($c) => ['id' => $c->id, 'texte' => $c->texte])->values()
|
||||
: [],
|
||||
] : null,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function repondre(Request $request, SessionQuiz $session): JsonResponse
|
||||
{
|
||||
$eleve = Auth::guard('eleve')->user();
|
||||
abort_if($session->quiz->classe_id !== $eleve->classe_id, 403);
|
||||
abort_if($session->statut !== SessionQuiz::EN_COURS || ! $session->question_quiz_courante_id, 409, 'Aucune question en cours.');
|
||||
|
||||
$question = $session->questionCourante;
|
||||
abort_if(
|
||||
ReponseQuiz::where('session_quiz_id', $session->id)
|
||||
->where('question_quiz_id', $question->id)
|
||||
->where('eleve_id', $eleve->id)
|
||||
->exists(),
|
||||
409,
|
||||
'Déjà répondu.'
|
||||
);
|
||||
|
||||
$donnees = $request->validate([
|
||||
'choix_id' => ['nullable', 'integer'],
|
||||
'reponse_vrai_faux' => ['nullable', 'boolean'],
|
||||
]);
|
||||
|
||||
$correcte = $question->estCorrecte($donnees['choix_id'] ?? null, $donnees['reponse_vrai_faux'] ?? null);
|
||||
|
||||
try {
|
||||
ReponseQuiz::create([
|
||||
'session_quiz_id' => $session->id,
|
||||
'question_quiz_id' => $question->id,
|
||||
'eleve_id' => $eleve->id,
|
||||
'choix_question_quiz_id' => $donnees['choix_id'] ?? null,
|
||||
'reponse_vrai_faux' => $donnees['reponse_vrai_faux'] ?? null,
|
||||
'correcte' => $correcte,
|
||||
'repondu_a' => now(),
|
||||
]);
|
||||
} catch (QueryException $e) {
|
||||
// Double clic : la contrainte d'unicité (session, question, élève)
|
||||
// a bloqué un second envoi arrivé en même temps que le premier.
|
||||
abort(409, 'Déjà répondu.');
|
||||
}
|
||||
|
||||
return response()->json(['correcte' => $correcte]);
|
||||
}
|
||||
}
|
||||
78
portail/app/Http/Controllers/SessionQuizController.php
Normal file
78
portail/app/Http/Controllers/SessionQuizController.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Classe;
|
||||
use App\Models\Quiz;
|
||||
use App\Models\SessionQuiz;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* Lancement en direct d'un quiz (chantier « vrai cockpit », étape 2) :
|
||||
* indépendant d'une séance de lab — le prof peut le lancer à tout moment
|
||||
* pour sa classe, les élèves le voient apparaître sur leur tableau de bord.
|
||||
*/
|
||||
class SessionQuizController extends Controller
|
||||
{
|
||||
public function lancer(Classe $classe, Quiz $quiz): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
abort_if($quiz->classe_id !== $classe->id, 404);
|
||||
abort_if($quiz->questions()->count() === 0, 422, 'Ce quiz n’a encore aucune question.');
|
||||
|
||||
$session = $quiz->sessions()->create(['statut' => SessionQuiz::EN_ATTENTE, 'demarree_a' => now()]);
|
||||
|
||||
return redirect()->route('sessions-quiz.show', $session);
|
||||
}
|
||||
|
||||
public function show(SessionQuiz $session): View
|
||||
{
|
||||
$this->verifierProprietaireSession($session);
|
||||
|
||||
return view('quiz.session-prof', [
|
||||
'session' => $session->load(['quiz.questions.choix', 'questionCourante.choix']),
|
||||
'decompte' => $session->decompteQuestionCourante(),
|
||||
'total_eleves' => $session->quiz->classe->eleves()->count(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function etat(SessionQuiz $session): JsonResponse
|
||||
{
|
||||
$this->verifierProprietaireSession($session);
|
||||
|
||||
return response()->json([
|
||||
'statut' => $session->statut,
|
||||
'question_id' => $session->question_quiz_courante_id,
|
||||
'decompte' => $session->decompteQuestionCourante(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function suivante(SessionQuiz $session): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaireSession($session);
|
||||
$session->passerALaQuestionSuivante();
|
||||
|
||||
return redirect()->route('sessions-quiz.show', $session);
|
||||
}
|
||||
|
||||
public function terminer(SessionQuiz $session): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaireSession($session);
|
||||
$session->terminer();
|
||||
|
||||
return redirect()->route('mes-classes.quiz.show', [$session->quiz->classe, $session->quiz]);
|
||||
}
|
||||
|
||||
private function verifierProprietaireSession(SessionQuiz $session): void
|
||||
{
|
||||
abort_if($session->quiz->classe->user_id !== Auth::guard('web')->id(), 403);
|
||||
}
|
||||
|
||||
private function verifierProprietaire(Classe $classe): void
|
||||
{
|
||||
abort_if($classe->user_id !== Auth::guard('web')->id(), 403);
|
||||
}
|
||||
}
|
||||
23
portail/app/Models/ChoixQuestionQuiz.php
Normal file
23
portail/app/Models/ChoixQuestionQuiz.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ChoixQuestionQuiz extends Model
|
||||
{
|
||||
protected $table = 'choix_questions_quiz';
|
||||
|
||||
protected $fillable = ['question_quiz_id', 'texte', 'est_correct', 'ordre'];
|
||||
|
||||
protected $casts = [
|
||||
'est_correct' => 'boolean',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<QuestionQuiz, $this> */
|
||||
public function question(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(QuestionQuiz::class, 'question_quiz_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -67,6 +67,12 @@ class Classe extends Model
|
|||
return $this->hasMany(Seance::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<Quiz, $this> */
|
||||
public function quizzes(): HasMany
|
||||
{
|
||||
return $this->hasMany(Quiz::class)->latest();
|
||||
}
|
||||
|
||||
public function estArchivee(): bool
|
||||
{
|
||||
return $this->archivee_a !== null;
|
||||
|
|
|
|||
48
portail/app/Models/QuestionQuiz.php
Normal file
48
portail/app/Models/QuestionQuiz.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class QuestionQuiz extends Model
|
||||
{
|
||||
public const QCM = 'qcm';
|
||||
|
||||
public const VRAI_FAUX = 'vrai_faux';
|
||||
|
||||
protected $table = 'questions_quiz';
|
||||
|
||||
protected $fillable = ['quiz_id', 'enonce', 'type', 'ordre', 'reponse_vrai_faux'];
|
||||
|
||||
protected $casts = [
|
||||
'reponse_vrai_faux' => 'boolean',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<Quiz, $this> */
|
||||
public function quiz(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Quiz::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<ChoixQuestionQuiz, $this> */
|
||||
public function choix(): HasMany
|
||||
{
|
||||
return $this->hasMany(ChoixQuestionQuiz::class)->orderBy('ordre');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrai si $choixId (QCM) ou $reponseVraiFaux (vrai/faux) est la bonne
|
||||
* réponse à cette question. Un seul des deux arguments compte, selon
|
||||
* le type de la question.
|
||||
*/
|
||||
public function estCorrecte(?int $choixId, ?bool $reponseVraiFaux): bool
|
||||
{
|
||||
if ($this->type === self::VRAI_FAUX) {
|
||||
return $reponseVraiFaux !== null && $reponseVraiFaux === $this->reponse_vrai_faux;
|
||||
}
|
||||
|
||||
return $choixId !== null && $this->choix()->where('id', $choixId)->where('est_correct', true)->exists();
|
||||
}
|
||||
}
|
||||
34
portail/app/Models/Quiz.php
Normal file
34
portail/app/Models/Quiz.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* Banque de questions réutilisable, propre à une classe (chantier « vrai
|
||||
* cockpit », étape 2 — 2026-10-13). Lancé en direct par SessionQuiz.
|
||||
*/
|
||||
class Quiz extends Model
|
||||
{
|
||||
protected $fillable = ['classe_id', 'titre'];
|
||||
|
||||
/** @return BelongsTo<Classe, $this> */
|
||||
public function classe(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Classe::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<QuestionQuiz, $this> */
|
||||
public function questions(): HasMany
|
||||
{
|
||||
return $this->hasMany(QuestionQuiz::class)->orderBy('ordre');
|
||||
}
|
||||
|
||||
/** @return HasMany<SessionQuiz, $this> */
|
||||
public function sessions(): HasMany
|
||||
{
|
||||
return $this->hasMany(SessionQuiz::class);
|
||||
}
|
||||
}
|
||||
37
portail/app/Models/ReponseQuiz.php
Normal file
37
portail/app/Models/ReponseQuiz.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ReponseQuiz extends Model
|
||||
{
|
||||
protected $table = 'reponses_quiz';
|
||||
|
||||
protected $fillable = ['session_quiz_id', 'question_quiz_id', 'eleve_id', 'choix_question_quiz_id', 'reponse_vrai_faux', 'correcte', 'repondu_a'];
|
||||
|
||||
protected $casts = [
|
||||
'reponse_vrai_faux' => 'boolean',
|
||||
'correcte' => 'boolean',
|
||||
'repondu_a' => 'datetime',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<SessionQuiz, $this> */
|
||||
public function session(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SessionQuiz::class, 'session_quiz_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<QuestionQuiz, $this> */
|
||||
public function question(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(QuestionQuiz::class, 'question_quiz_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Eleve, $this> */
|
||||
public function eleve(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Eleve::class);
|
||||
}
|
||||
}
|
||||
96
portail/app/Models/SessionQuiz.php
Normal file
96
portail/app/Models/SessionQuiz.php
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* Lancement en direct d'un Quiz (chantier « vrai cockpit », étape 2).
|
||||
* Indépendant d'une séance de lab : les élèves de la classe la voient
|
||||
* apparaître sur leur tableau de bord dès qu'elle passe en_cours.
|
||||
*/
|
||||
class SessionQuiz extends Model
|
||||
{
|
||||
public const EN_ATTENTE = 'en_attente';
|
||||
|
||||
public const EN_COURS = 'en_cours';
|
||||
|
||||
public const TERMINEE = 'terminee';
|
||||
|
||||
protected $table = 'sessions_quiz';
|
||||
|
||||
protected $fillable = ['quiz_id', 'statut', 'question_quiz_courante_id', 'demarree_a', 'terminee_a'];
|
||||
|
||||
protected $casts = [
|
||||
'demarree_a' => 'datetime',
|
||||
'terminee_a' => 'datetime',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<Quiz, $this> */
|
||||
public function quiz(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Quiz::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<QuestionQuiz, $this> */
|
||||
public function questionCourante(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(QuestionQuiz::class, 'question_quiz_courante_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<ReponseQuiz, $this> */
|
||||
public function reponses(): HasMany
|
||||
{
|
||||
return $this->hasMany(ReponseQuiz::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passe à la question suivante du quiz (dans l'ordre), ou termine la
|
||||
* session s'il n'y en a plus. Idempotent : rejouable sans effet si déjà
|
||||
* terminée.
|
||||
*/
|
||||
public function passerALaQuestionSuivante(): void
|
||||
{
|
||||
if ($this->statut === self::TERMINEE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$questions = $this->quiz->questions;
|
||||
$indiceActuel = $this->question_quiz_courante_id
|
||||
? $questions->search(fn ($q) => $q->id === $this->question_quiz_courante_id)
|
||||
: -1;
|
||||
$suivante = $questions->get($indiceActuel + 1);
|
||||
|
||||
if ($suivante) {
|
||||
$this->forceFill(['statut' => self::EN_COURS, 'question_quiz_courante_id' => $suivante->id])->save();
|
||||
} else {
|
||||
$this->terminer();
|
||||
}
|
||||
}
|
||||
|
||||
public function terminer(): void
|
||||
{
|
||||
if ($this->statut !== self::TERMINEE) {
|
||||
$this->forceFill(['statut' => self::TERMINEE, 'terminee_a' => now()])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Décompte des réponses à la question en cours, pour le panneau du
|
||||
* prof : combien d'élèves ont déjà répondu, combien ont juste.
|
||||
*
|
||||
* @return array{repondu: int, correctes: int}
|
||||
*/
|
||||
public function decompteQuestionCourante(): array
|
||||
{
|
||||
if (! $this->question_quiz_courante_id) {
|
||||
return ['repondu' => 0, 'correctes' => 0];
|
||||
}
|
||||
|
||||
$reponses = $this->reponses->where('question_quiz_id', $this->question_quiz_courante_id);
|
||||
|
||||
return ['repondu' => $reponses->count(), 'correctes' => $reponses->where('correcte', true)->count()];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue