Paillasse/portail/resources/views/seances/attente-eleve.blade.php

165 lines
10 KiB
PHP
Raw Normal View History

@extends('layouts.app')
@section('titre', "Salle d'attente")
@php($etats = \App\Models\LabSession::class)
@section('contenu')
<div class="mx-auto max-w-5xl px-5 py-14 lg:px-10">
<p class="font-mono text-sm font-semibold">{{ $seance->lab->titre }}</p>
@if ($seance->visioOuverte())
<p class="mt-3"><a href="{{ route('seances.visio.eleve', $seance) }}" class="inline-flex h-11 items-center rounded-full bg-[#0b57d0] px-5 font-bold text-white">Rejoindre la visio de la classe</a></p>
@endif
@if ($labSession->etat === $etats::EN_ATTENTE)
<div class="py-20 text-center">
<h1 class="text-4xl font-extrabold tracking-tight">En attente que le prof t'admette…</h1>
<p class="mt-4 font-semibold">Tes machines s'ouvriront ici dès qu'il t'aura admis.</p>
</div>
@elseif ($labSession->etat === $etats::ADMIS)
<div class="py-20 text-center">
<h1 class="text-4xl font-extrabold tracking-tight">Préparation de tes VM…</h1>
<p class="mt-4 font-semibold">Ça prend généralement moins d'une minute.</p>
</div>
@elseif ($labSession->etat === $etats::ECHEC)
<div class="py-20 text-center">
<h1 class="text-4xl font-extrabold tracking-tight">Tes machines n'ont pas pu être préparées</h1>
<p class="mt-4 font-semibold">Ton prof le voit et peut relancer. Cette page se mettra à jour toute seule.</p>
</div>
@elseif ($labSession->etat === $etats::ACTIF && $labSession->machines->isNotEmpty())
@if ($seance->mode_examen)
<div id="bandeau-examen" class="mb-4 flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-[#A63D2A] bg-[#fce8e6] p-4">
<p class="font-extrabold">Mode examen · temps restant : <span id="temps-restant" class="font-mono">…</span></p>
<button type="button" id="bouton-plein-ecran" class="inline-flex h-10 items-center rounded-full bg-black px-4 font-bold text-white">Passer en plein écran</button>
</div>
<p class="mb-4 text-sm font-semibold text-[#5f6368]">Reste sur cette page : les changements d'onglet et les sorties du plein écran sont signalés à ton prof.</p>
@endif
<h1 class="mt-2 text-3xl font-extrabold tracking-tight">Tes machines</h1>
<div class="mt-4 flex gap-2" role="tablist">
@foreach ($labSession->machines as $i => $machine)
<button type="button" data-onglet="{{ $machine->id }}" class="onglet-machine rounded-lg border border-[#dadce0] px-4 py-2 font-mono font-bold {{ $i === 0 ? 'bg-[#0b57d0] text-white' : 'bg-white' }}">{{ $machine->nom }}</button>
@endforeach
</div>
@foreach ($labSession->machines as $i => $machine)
<div data-panneau="{{ $machine->id }}" @if ($i !== 0) hidden @endif class="mt-2 h-[480px] overflow-hidden rounded-2xl border border-[#e3e5e8]" style="background:#13233A">
<div id="terminal-{{ $machine->id }}" class="h-full"></div>
</div>
@endforeach
<section class="mt-6 rounded-2xl border border-[#e3e5e8] bg-white p-6" aria-labelledby="t-verification">
<div class="flex flex-wrap items-center justify-between gap-3">
<h2 id="t-verification" class="text-2xl font-extrabold">Vérification</h2>
<form action="{{ route('lab-sessions.corriger', $labSession) }}" method="post" id="form-verifier">
@csrf
<button type="submit" class="inline-flex h-11 items-center rounded-full bg-black px-5 font-bold text-white">Vérifier mon travail</button>
</form>
</div>
@if (session('correction_indisponible'))
<p class="mt-4 rounded-xl bg-[#fce8e6] p-3 font-semibold">La vérification est indisponible pour le moment. Réessaie dans une minute.</p>
@endif
@if ($correction = $labSession->derniereCorrection)
<p class="mt-4 text-lg font-extrabold">
{{ $correction->reussi ? 'Réussi' : 'Pas encore réussi' }}
<span class="font-mono">· {{ $correction->note }}/{{ $correction->note_max }}</span>
</p>
<ul class="mt-3 grid gap-2">
@foreach ($correction->details as $detail)
<li class="rounded-xl border border-[#e3e5e8] p-3">
<span class="font-mono font-bold">{{ $detail['reussi'] ? '✓' : '✗' }}</span>
<span class="font-bold">{{ $detail['titre'] }}</span>
<span class="font-mono text-sm">({{ $detail['points'] }} pt{{ $detail['points'] > 1 ? 's' : '' }})</span>
@if ($detail['message'])
<p class="mt-1 font-semibold text-[#A63D2A]">{{ $detail['message'] }}</p>
@endif
</li>
@endforeach
</ul>
@endif
</section>
@vite('resources/js/console.js')
<script nonce="{{ Vite::cspNonce() }}" src="{{ asset('js/console-reelle.js') }}"></script>
<script nonce="{{ Vite::cspNonce() }}">
// Après les modules Vite (différés) : window.Terminal est défini.
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.onglet-machine').forEach(function (bouton) {
bouton.addEventListener('click', function () {
var id = bouton.dataset.onglet;
document.querySelectorAll('.onglet-machine').forEach(function (b) { b.classList.toggle('bg-[#0b57d0]', b === bouton); b.classList.toggle('text-white', b === bouton); b.classList.toggle('bg-white', b !== bouton); });
document.querySelectorAll('[data-panneau]').forEach(function (p) { p.hidden = p.dataset.panneau !== id; });
});
});
document.getElementById('form-verifier').addEventListener('submit', function (e) {
var bouton = e.target.querySelector('button');
bouton.disabled = true;
bouton.textContent = 'Vérification en cours…';
});
@foreach ($labSession->machines as $machine)
fetch('{{ route('console.eleve', [$labSession, $machine]) }}', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
}).then(function (r) { return r.json(); }).then(function (d) {
window.SyllabusConsoleReelle.mount(document.getElementById('terminal-{{ $machine->id }}'), { jeton: d.jeton, lectureSeule: false, reveil: true });
});
@endforeach
});
</script>
@if ($seance->mode_examen)
<script nonce="{{ Vite::cspNonce() }}">
(function () {
var finA = new Date(@json($labSession->expire_a?->toIso8601String()));
var URL_EVENEMENT = @json(route('lab-sessions.evenements-examen', $labSession));
var CSRF = @json(csrf_token());
function signaler(type) {
fetch(URL_EVENEMENT, {
method: 'POST', keepalive: true,
headers: { 'X-CSRF-TOKEN': CSRF, 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ type: type }),
}).catch(function () {});
}
function mettreAJourTemps() {
var restantMs = finA - new Date();
var affichage = document.getElementById('temps-restant');
if (restantMs <= 0) { affichage.textContent = '0 min'; location.reload(); return; }
affichage.textContent = Math.ceil(restantMs / 60000) + ' min';
}
mettreAJourTemps();
setInterval(mettreAJourTemps, 10000);
document.getElementById('bouton-plein-ecran').addEventListener('click', function () {
if (document.documentElement.requestFullscreen) document.documentElement.requestFullscreen().catch(function () {});
});
document.addEventListener('visibilitychange', function () {
if (document.hidden) signaler('changement_onglet');
});
document.addEventListener('fullscreenchange', function () {
if (!document.fullscreenElement) signaler('sortie_plein_ecran');
});
})();
</script>
@endif
@else
<div class="py-20 text-center">
<h1 class="text-4xl font-extrabold tracking-tight">Cette séance est terminée pour toi</h1>
<p class="mt-4 font-semibold">Tes machines ont été supprimées.</p>
</div>
@endif
</div>
@if (in_array($labSession->etat, [$etats::EN_ATTENTE, $etats::ADMIS, $etats::ECHEC], true))
<script nonce="{{ Vite::cspNonce() }}">
(function () {
var etatAffiche = @json($labSession->etat);
setInterval(function () {
fetch('{{ route('seances.eleve.etat', $seance) }}', { headers: { 'Accept': 'application/json' } })
.then(function (r) { return r.json(); })
.then(function (d) { if (d.etat !== etatAffiche) location.reload(); })
.catch(function () {});
}, 1000);
})();
</script>
@endif
@endsection