rapproche encore le tableau de bord de la démo /cockpit : avancement par vérification, mains levées avec action réelle, progression de la séance

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-12 23:20:28 +02:00
parent 0573894721
commit ee079ddffb
6 changed files with 178 additions and 6 deletions

View file

@ -3,11 +3,13 @@
namespace App\Http\Controllers;
use App\Models\Classe;
use App\Models\Correction;
use App\Models\DemandeInscription;
use App\Models\Eleve;
use App\Models\LabSession;
use App\Models\Seance;
use App\Services\GenerateurCompteEleve;
use App\Services\Labs\DefinitionLab;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@ -48,17 +50,44 @@ class ClasseController extends Controller
->sortByDesc(fn ($s) => $s->statut === Seance::EN_COURS)
->first();
// Déjà chargées par le eager loading ci-dessus (labSessions.derniereCorrection) :
// repasser par une requête séparée perdait le type précis pour l'analyse statique.
$corrections = $seance
? LabSession::query()->whereIn('id', $seance->labSessions->pluck('id'))->whereHas('derniereCorrection')->with('derniereCorrection')->get()->pluck('derniereCorrection')
? $seance->labSessions->map(fn (LabSession $ls) => $ls->derniereCorrection)->filter()->values()
: collect();
// Répartition par vérification du lab (équivalent réel du « répartition
// par étape » de la démo) : l'ordre et les titres viennent du lab.yaml,
// le compte de réussite des vraies corrections. Jamais de bonus ici
// (ils ajoutent des points, ils ne font pas partie du parcours normal).
$avancement = collect();
if ($seance && $corrections->isNotEmpty()) {
$definition = DefinitionLab::depuis($seance->lab->identifiant);
$avancement = collect($definition->verifications)
->reject(fn ($v) => $v['bonus'])
->map(fn ($v) => [
'titre' => $v['titre'],
'reussi' => $corrections->filter(fn (Correction $c) => collect($c->details)->firstWhere('id', $v['id'])['reussi'] ?? false)->count(),
'total' => $corrections->count(),
]);
}
// Progression dans le temps de la séance, d'après la durée estimée
// du lab (Lab::duree_min) : absente si l'une des deux manque, jamais
// une estimation inventée.
$progressionSeance = ($seance?->demarree_a && $seance->lab->duree_min)
? min(100, (int) round($seance->demarree_a->diffInMinutes(now()) / $seance->lab->duree_min * 100))
: null;
return view('mes-classes.show', [
'classes' => Auth::guard('web')->user()->classes()->withCount('eleves')->orderBy('nom')->get(),
'classe' => $classe->load('eleves'),
'demandes' => $classe->demandesInscription()->where('statut', DemandeInscription::EN_ATTENTE)->oldest()->get(),
'seance' => $seance,
'avancement' => $avancement,
'progressionSeance' => $progressionSeance,
'reussiteMoyenne' => $corrections->isNotEmpty() ? (int) round($corrections->avg(fn ($c) => $c->note_max > 0 ? $c->note / $c->note_max * 100 : 0)) : null,
'mainsLevees' => ($seance && $seance->visioOuverte()) ? VisioController::nombreMainsLevees($seance) : null,
'mains' => ($seance && $seance->visioOuverte()) ? VisioController::mainsLevees($seance) : [],
]);
}

View file

@ -214,7 +214,26 @@ class VisioController extends Controller
public static function nombreMainsLevees(Seance $seance): int
{
return count((Cache::get(self::cleEtat($seance)) ?? [])['mains'] ?? []);
return count(self::mainsLevees($seance));
}
/**
* Mains levées, premier arrivé en premier (même tri que etatProf) : nom
* déjà minimisé (prénom + initiale), rien de plus que ce qu'affiche
* déjà la page de visio.
*
* @return list<array{eleve_id: int, nom: string, depuis: int}>
*/
public static function mainsLevees(Seance $seance): array
{
/** @var array<int, array{nom: string, depuis: int}> $mains */
$mains = (Cache::get(self::cleEtat($seance)) ?? [])['mains'] ?? [];
return collect($mains)
->sortBy('depuis')
->map(fn ($m, $id) => ['eleve_id' => (int) $id, 'nom' => $m['nom'], 'depuis' => $m['depuis']])
->values()
->all();
}
private function cle(Seance $seance): string

View file

@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* Une passe de vérification d'une session de lab (bouton « Vérifier mon
* travail »). details : une entrée par vérification du lab.yaml, avec
* id, titre, points, reussi et message (message_echec du lab si échouée).
*
* @property-read array<int, array{id: string, titre: string, points: int, bonus: bool, reussi: bool, message: ?string}> $details
*/
class Correction extends Model
{