permet aux élèves de rejoindre leur classe avec son code, et les fiches en lot

L'élève saisit le code de classe et son prénom, le prof accepte, l'élève
voit une seule fois son identifiant et son code. Aucun e-mail. Le prof
peut aussi régénérer les fiches de plusieurs élèves d'un coup. Procédure
pour prouver la copie de la clé des sauvegardes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-09 22:18:53 +02:00
parent 1ac8d4773a
commit dd4f9c3c55
21 changed files with 573 additions and 26 deletions

View file

@ -18,6 +18,33 @@
<p class="mt-4 border border-[#e3e5e8] rounded-2xl bg-[#e6f4ea] p-4 font-semibold">Classe créée. Ajoute maintenant tes élèves ci-dessous.</p>
@endif
@unless ($classe->estArchivee())
<section class="mt-10 grid gap-6 rounded-2xl border border-[#e3e5e8] bg-white p-6 md:grid-cols-[1fr_auto]" aria-labelledby="t-rejoindre">
<div>
<h2 id="t-rejoindre" class="text-2xl font-extrabold">Faire rejoindre tes élèves</h2>
<p class="mt-2 font-semibold">Projette ce code ou ce QR. Chaque élève va sur <span class="font-mono">{{ preg_replace('#^https?://#', '', route('rejoindre')) }}</span>, tape le code et son prénom ; tu acceptes sa demande ici, et il reçoit à l'écran son identifiant et son code secret. Aucun e-mail.</p>
<p class="mt-4 font-mono text-5xl font-bold tracking-[0.3em]">{{ $classe->code_classe }}</p>
<h3 class="mt-6 text-lg font-extrabold">Demandes en attente ({{ $demandes->count() }})</h3>
@if ($demandes->isEmpty())
<p class="mt-2 font-semibold text-[#5f6368]">Aucune pour l'instant. Cette liste se met à jour toute seule.</p>
@else
<ul class="mt-2 grid gap-2">
@foreach ($demandes as $demande)
<li class="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-[#e3e5e8] p-3">
<span class="font-extrabold">{{ $demande->prenom }}@if ($demande->nom) {{ $demande->nom }}@endif <span class="font-mono text-sm font-semibold text-[#5f6368]">· {{ $demande->created_at->diffForHumans() }}</span></span>
<span class="flex gap-2">
<form action="{{ route('mes-classes.demandes.accepter', [$classe, $demande]) }}" method="post">@csrf<button type="submit" class="inline-flex h-10 items-center rounded-full bg-[#0b57d0] px-4 font-bold text-white">Accepter</button></form>
<form action="{{ route('mes-classes.demandes.refuser', [$classe, $demande]) }}" method="post">@csrf<button type="submit" class="inline-flex h-10 items-center rounded-full border border-[#dadce0] px-4 font-bold">Refuser</button></form>
</span>
</li>
@endforeach
</ul>
@endif
</div>
<div class="justify-self-center">{!! \App\Support\QrCode::svg(route('rejoindre', ['code' => $classe->code_classe])) !!}</div>
</section>
@endunless
@if (session('fiches'))
<section class="mt-10 border border-[#e3e5e8] rounded-2xl bg-[#fef7e0] p-6 print:border-0 print:bg-white" aria-labelledby="t-fiches">
<div class="flex flex-wrap items-center justify-between gap-3 print:hidden">
@ -69,16 +96,26 @@
</div>
<section class="mt-10 border border-[#e3e5e8] rounded-2xl bg-white" aria-labelledby="t-eleves">
<h2 id="t-eleves" class="p-6 pb-0 text-2xl font-extrabold">Élèves ({{ $classe->eleves->count() }})</h2>
<div class="flex flex-wrap items-center justify-between gap-3 p-6 pb-0">
<h2 id="t-eleves" class="text-2xl font-extrabold">Élèves ({{ $classe->eleves->count() }})</h2>
@if ($classe->eleves->isNotEmpty())
<form id="form-fiches" action="{{ route('mes-classes.fiches', $classe) }}" method="post" data-confirmer="Générer de nouvelles fiches pour les élèves cochés ? Leurs anciens codes ne fonctionneront plus.">
@csrf
<button type="submit" class="inline-flex h-10 items-center rounded-full border border-[#0b57d0] px-4 font-bold text-[#0b57d0]">Nouvelles fiches pour la sélection</button>
</form>
@endif
</div>
@error('eleves')<p class="px-6 pt-2 font-semibold text-[#A63D2A]">Coche au moins un élève.</p>@enderror
@if ($classe->eleves->isEmpty())
<p class="p-6 font-semibold">Aucun élève pour l'instant.</p>
@else
<div class="overflow-x-auto">
<table class="mt-4 w-full min-w-[480px] text-left">
<thead class="bg-[#0b57d0] text-white"><tr><th class="px-4 py-2">Élève</th><th class="px-4 py-2 font-mono text-xs">Identifiant</th><th class="px-4 py-2">Dernière connexion</th><th class="px-4 py-2"></th></tr></thead>
<thead class="bg-[#0b57d0] text-white"><tr><th class="px-4 py-2"><input type="checkbox" data-tout-cocher aria-label="Tout sélectionner"></th><th class="px-4 py-2">Élève</th><th class="px-4 py-2 font-mono text-xs">Identifiant</th><th class="px-4 py-2">Dernière connexion</th><th class="px-4 py-2"></th></tr></thead>
<tbody class="divide-y divide-[#e3e5e8]">
@foreach ($classe->eleves as $eleve)
<tr>
<td class="px-4 py-3"><input type="checkbox" name="eleves[]" value="{{ $eleve->id }}" form="form-fiches" aria-label="Sélectionner {{ $eleve->prenom }}"></td>
<td class="px-4 py-3 font-extrabold">{{ $eleve->nomAffiche() }}</td>
<td class="px-4 py-3 font-mono text-sm">{{ $eleve->identifiant }}</td>
<td class="px-4 py-3 font-semibold">{{ $eleve->derniere_connexion_a?->diffForHumans() ?? 'jamais' }}</td>
@ -165,6 +202,22 @@
@endif
</div>
<script nonce="{{ Vite::cspNonce() }}">
document.querySelectorAll('[data-tout-cocher]').forEach(function (c) {
c.addEventListener('change', function () { document.querySelectorAll('input[name="eleves[]"]').forEach(function (x) { x.checked = c.checked; }); });
});
@unless ($classe->estArchivee())
(function () {
var vues = {{ $demandes->count() }};
setInterval(function () {
fetch('{{ route('mes-classes.demandes.nombre', $classe) }}', { headers: { 'Accept': 'application/json' } })
.then(function (r) { return r.json(); })
.then(function (d) {
var occupe = document.activeElement && /INPUT|TEXTAREA|SELECT/.test(document.activeElement.tagName);
if (d.en_attente !== vues && !occupe) location.reload();
}).catch(function () {});
}, 4000);
})();
@endunless
document.querySelectorAll('[data-imprimer]').forEach(function (b) { b.addEventListener('click', function () { window.print(); }); });
document.querySelectorAll('form[data-confirmer]').forEach(function (f) {
f.addEventListener('submit', function (e) { if (!window.confirm(f.dataset.confirmer)) e.preventDefault(); });