feat: add full screen mode
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
<div><kbd class="bg-gray-700 px-1 rounded">F1</kbd> : Afficher/Masquer cette aide</div>
|
<div><kbd class="bg-gray-700 px-1 rounded">F1</kbd> : Afficher/Masquer cette aide</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-6">
|
<div class="w-full max-w-none space-y-6">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium mb-2 inline-block">
|
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium mb-2 inline-block">
|
||||||
@@ -34,29 +34,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if not grading_elements %}
|
|
||||||
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
|
||||||
<div class="flex">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-medium text-yellow-800">Aucun élément de notation</h3>
|
|
||||||
<div class="mt-2 text-sm text-yellow-700">
|
|
||||||
<p>Cette évaluation n'a pas encore d'éléments de notation configurés. Vous devez d'abord créer des exercices et leurs éléments de notation.</p>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4">
|
|
||||||
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="text-sm font-medium text-yellow-800 underline hover:text-yellow-900">
|
|
||||||
Configurer l'évaluation →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<form method="POST" action="{{ url_for('grading.save_grades', assessment_id=assessment.id) }}" class="space-y-6" id="grading-form">
|
|
||||||
<!-- Guide de saisie unifié moderne -->
|
<!-- Guide de saisie unifié moderne -->
|
||||||
<div class="bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200 rounded-lg p-4">
|
<div class="bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200 rounded-lg p-4">
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
@@ -88,7 +65,44 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right flex items-center space-x-3">
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% if not grading_elements %}
|
||||||
|
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<h3 class="text-sm font-medium text-yellow-800">Aucun élément de notation</h3>
|
||||||
|
<div class="mt-2 text-sm text-yellow-700">
|
||||||
|
<p>Cette évaluation n'a pas encore d'éléments de notation configurés. Vous devez d'abord créer des exercices et leurs éléments de notation.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="text-sm font-medium text-yellow-800 underline hover:text-yellow-900">
|
||||||
|
Configurer l'évaluation →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<form method="POST" action="{{ url_for('grading.save_grades', assessment_id=assessment.id) }}" class="space-y-6" id="grading-form">
|
||||||
|
<!-- Tableau de saisie -->
|
||||||
|
<div class="bg-white shadow rounded-lg">
|
||||||
|
<div class="px-4 py-3 border-b border-gray-200 flex justify-between items-center">
|
||||||
|
<h2 class="text-base font-medium text-gray-900">Grille de notation</h2>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<button type="button" onclick="toggleFullscreen()" id="fullscreen-btn" class="text-xs bg-purple-100 hover:bg-purple-200 text-purple-800 px-3 py-1 rounded transition-colors flex items-center space-x-1" title="Passer en plein écran">
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Plein écran</span>
|
||||||
|
</button>
|
||||||
<button type="button" onclick="toggleKeyboardHelp()" class="text-xs bg-blue-100 hover:bg-blue-200 text-blue-800 px-2 py-1 rounded transition-colors">
|
<button type="button" onclick="toggleKeyboardHelp()" class="text-xs bg-blue-100 hover:bg-blue-200 text-blue-800 px-2 py-1 rounded transition-colors">
|
||||||
📋 F1
|
📋 F1
|
||||||
</button>
|
</button>
|
||||||
@@ -98,49 +112,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filtre des élèves -->
|
|
||||||
<div class="bg-white shadow rounded-lg overflow-hidden mb-4">
|
|
||||||
<div class="px-4 py-3 border-b border-gray-200">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h3 class="text-sm font-medium text-gray-700">Filtrer les élèves</h3>
|
|
||||||
<span id="student-count" class="text-xs text-gray-500">{{ students|length }} élève(s)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-4">
|
|
||||||
<div class="relative">
|
|
||||||
<input type="text"
|
|
||||||
id="student-filter"
|
|
||||||
placeholder="Rechercher un élève par nom ou prénom..."
|
|
||||||
class="w-full px-3 py-2 pl-10 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200">
|
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
||||||
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<button type="button"
|
|
||||||
id="clear-filter"
|
|
||||||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 hidden"
|
|
||||||
onclick="clearStudentFilter()">
|
|
||||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tableau de saisie -->
|
|
||||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
|
||||||
<div class="px-4 py-3 border-b border-gray-200">
|
|
||||||
<h2 class="text-base font-medium text-gray-900">Grille de notation</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="grading-table-container">
|
||||||
<table class="min-w-full divide-y divide-gray-200 table-fixed">
|
<table class="min-w-full divide-y divide-gray-200 table-fixed grading-table">
|
||||||
<!-- En-têtes des exercices -->
|
<!-- En-têtes des exercices -->
|
||||||
<thead class="bg-gradient-to-r from-indigo-50 to-purple-50">
|
<thead class="bg-gradient-to-r from-indigo-50 to-purple-50 sticky top-0 z-50 shadow-sm">
|
||||||
<tr class="border-b-2 border-indigo-200">
|
<tr class="border-b-2 border-indigo-200">
|
||||||
<th scope="col" class="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gradient-to-r from-indigo-50 to-purple-50 border-r border-indigo-200 w-48">
|
<th scope="col" class="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gradient-to-r from-indigo-50 to-purple-50 border-r border-indigo-200 w-48">
|
||||||
Élève
|
Élève
|
||||||
@@ -164,10 +140,32 @@
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<!-- En-têtes des éléments de notation -->
|
<!-- En-têtes des éléments de notation -->
|
||||||
<thead class="bg-gray-50">
|
<thead class="bg-gray-50 sticky z-40 shadow-sm" style="top: 3rem;">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gray-50 border-r border-gray-200 w-48">
|
<th scope="col" class="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gray-50 border-r border-gray-200 w-48">
|
||||||
<!-- Vide pour alignement -->
|
<!-- Filtre des élèves intégré -->
|
||||||
|
<div class="relative w-full">
|
||||||
|
<input type="text"
|
||||||
|
id="student-filter"
|
||||||
|
placeholder="Filtrer les élèves..."
|
||||||
|
class="w-full px-2 py-1 pl-6 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-blue-500 bg-white">
|
||||||
|
<div class="absolute inset-y-0 left-0 pl-2 flex items-center pointer-events-none">
|
||||||
|
<svg class="h-3 w-3 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
id="clear-filter"
|
||||||
|
class="absolute inset-y-0 right-0 pr-2 flex items-center text-gray-400 hover:text-gray-600 hidden"
|
||||||
|
onclick="clearStudentFilter()">
|
||||||
|
<svg class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-1">
|
||||||
|
<span id="student-count" class="text-xs text-gray-500">{{ students|length }} élève(s)</span>
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
{% set current_exercise = '' %}
|
{% set current_exercise = '' %}
|
||||||
{% for element in grading_elements %}
|
{% for element in grading_elements %}
|
||||||
@@ -309,6 +307,61 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- CSS custom pour forcer le sticky et pleine largeur -->
|
||||||
|
<style>
|
||||||
|
/* Pleine largeur pour cette page */
|
||||||
|
|
||||||
|
/* Mode plein écran */
|
||||||
|
.fullscreen-mode {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-overlay {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
background: white !important;
|
||||||
|
padding: 1rem !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-overlay .grading-table-container {
|
||||||
|
flex: 1 !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grading-table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.grading-table thead {
|
||||||
|
position: -webkit-sticky;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 50;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.grading-table thead:nth-child(2) {
|
||||||
|
top: 3rem;
|
||||||
|
z-index: 40;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.grading-table th[scope="col"]:first-child {
|
||||||
|
position: -webkit-sticky;
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 51;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- Légende -->
|
<!-- Légende -->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -409,7 +462,11 @@ function setupKeyboardNavigation() {
|
|||||||
// Ctrl+S : Sauvegarder
|
// Ctrl+S : Sauvegarder
|
||||||
if (e.ctrlKey && e.key === 's') {
|
if (e.ctrlKey && e.key === 's') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
saveForm();
|
if (isFullscreen) {
|
||||||
|
saveFormFromFullscreen();
|
||||||
|
} else {
|
||||||
|
saveForm();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+Z : Annuler dernière modification
|
// Ctrl+Z : Annuler dernière modification
|
||||||
@@ -785,6 +842,75 @@ function saveForm() {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sauvegarder depuis le mode plein écran
|
||||||
|
function saveFormFromFullscreen() {
|
||||||
|
const form = document.getElementById('grading-form');
|
||||||
|
const saveButtonFs = document.getElementById('save-button-fs');
|
||||||
|
const saveTextFs = document.getElementById('save-text-fs');
|
||||||
|
const saveSpinnerFs = document.getElementById('save-spinner-fs');
|
||||||
|
|
||||||
|
if (!form) {
|
||||||
|
console.error('Formulaire non trouvé');
|
||||||
|
showToast('Erreur : formulaire non trouvé', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation de sauvegarde en mode plein écran
|
||||||
|
if (saveButtonFs) {
|
||||||
|
saveButtonFs.disabled = true;
|
||||||
|
if (saveTextFs) saveTextFs.textContent = 'Sauvegarde...';
|
||||||
|
if (saveSpinnerFs) saveSpinnerFs.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchroniser les données du mode plein écran vers le formulaire original
|
||||||
|
syncDataFromFullscreenToOriginal();
|
||||||
|
|
||||||
|
// Soumettre le formulaire original
|
||||||
|
setTimeout(() => {
|
||||||
|
form.submit();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchroniser les données entre le mode plein écran et le formulaire original
|
||||||
|
function syncDataFromFullscreenToOriginal() {
|
||||||
|
if (!isFullscreen || !fullscreenOverlay) return;
|
||||||
|
|
||||||
|
// Synchroniser tous les champs de saisie
|
||||||
|
const fsInputs = fullscreenOverlay.querySelectorAll('.grading-input, .comment-input');
|
||||||
|
|
||||||
|
fsInputs.forEach(fsInput => {
|
||||||
|
const name = fsInput.name;
|
||||||
|
if (name) {
|
||||||
|
const originalInput = document.querySelector(`[name="${name}"]`);
|
||||||
|
if (originalInput) {
|
||||||
|
originalInput.value = fsInput.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Données synchronisées du plein écran vers le formulaire original');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchroniser les données du formulaire original vers le mode plein écran
|
||||||
|
function syncDataFromOriginalToFullscreen() {
|
||||||
|
if (!isFullscreen || !fullscreenOverlay) return;
|
||||||
|
|
||||||
|
// Synchroniser tous les champs de saisie
|
||||||
|
const originalInputs = document.querySelectorAll('#grading-form .grading-input, #grading-form .comment-input');
|
||||||
|
|
||||||
|
originalInputs.forEach(originalInput => {
|
||||||
|
const name = originalInput.name;
|
||||||
|
if (name) {
|
||||||
|
const fsInput = fullscreenOverlay.querySelector(`[name="${name}"]`);
|
||||||
|
if (fsInput) {
|
||||||
|
fsInput.value = originalInput.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Données synchronisées du formulaire original vers le plein écran');
|
||||||
|
}
|
||||||
|
|
||||||
// Afficher/Masquer l'aide clavier
|
// Afficher/Masquer l'aide clavier
|
||||||
function toggleKeyboardHelp() {
|
function toggleKeyboardHelp() {
|
||||||
const help = document.getElementById('keyboard-help');
|
const help = document.getElementById('keyboard-help');
|
||||||
@@ -1031,6 +1157,192 @@ function clearStudentFilter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gestion du mode plein écran
|
||||||
|
let isFullscreen = false;
|
||||||
|
let fullscreenOverlay = null;
|
||||||
|
|
||||||
|
function toggleFullscreen() {
|
||||||
|
const btn = document.getElementById('fullscreen-btn');
|
||||||
|
const btnText = btn.querySelector('span');
|
||||||
|
const btnIcon = btn.querySelector('svg path');
|
||||||
|
const tableContainer = document.querySelector('.bg-white.shadow.rounded-lg');
|
||||||
|
|
||||||
|
if (!isFullscreen) {
|
||||||
|
// Créer l'overlay plein écran
|
||||||
|
fullscreenOverlay = document.createElement('div');
|
||||||
|
fullscreenOverlay.className = 'fullscreen-overlay';
|
||||||
|
|
||||||
|
// Créer la structure plein écran optimisée
|
||||||
|
fullscreenOverlay.innerHTML = `
|
||||||
|
<div class="h-full flex flex-col justify-between">
|
||||||
|
<!-- Header avec contrôles -->
|
||||||
|
<div class="bg-white shadow rounded-t-lg border-b border-gray-200">
|
||||||
|
<div class="px-4 py-3 flex justify-between items-center">
|
||||||
|
<h2 class="text-base font-medium text-gray-900">Grille de notation</h2>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<button type="button" onclick="toggleFullscreen()" class="text-xs bg-red-100 hover:bg-red-200 text-red-800 px-3 py-1 rounded transition-colors flex items-center space-x-1" title="Quitter le plein écran">
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 3v3a2 2 0 01-2 2H3m18 0h-3a2 2 0 01-2-2V3m0 18v-3a2 2 0 012-2h3M3 16h3a2 2 0 012 2v3"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Quitter</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="toggleKeyboardHelp()" class="text-xs bg-blue-100 hover:bg-blue-200 text-blue-800 px-2 py-1 rounded transition-colors">
|
||||||
|
📋 F1
|
||||||
|
</button>
|
||||||
|
<div class="text-xs">
|
||||||
|
<div class="text-blue-700">Progression :</div>
|
||||||
|
<div id="progress-indicator-fs" class="font-semibold text-blue-900">0 / {{ (students|length * grading_elements|length) }} champs</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tableau central extensible -->
|
||||||
|
<div class="flex-1 bg-white shadow-none grading-table-container" style="min-height: 0;">
|
||||||
|
<!-- Le contenu du tableau sera cloné ici -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer de sauvegarde fixe en bas -->
|
||||||
|
<div class="bg-white shadow rounded-b-lg border-t border-gray-200">
|
||||||
|
<div class="px-6 py-4 bg-gray-50 flex justify-between items-center">
|
||||||
|
<div class="flex items-center space-x-4 text-sm text-gray-600">
|
||||||
|
<div id="save-status-fs" class="flex items-center">
|
||||||
|
<span class="w-2 h-2 bg-gray-400 rounded-full mr-2"></span>
|
||||||
|
<span>Non sauvegardé</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-3">
|
||||||
|
<button type="button" onclick="resetForm()" class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors">
|
||||||
|
Réinitialiser
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="saveFormFromFullscreen()" id="save-button-fs" class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors flex items-center">
|
||||||
|
<span id="save-text-fs">Sauvegarder les notes</span>
|
||||||
|
<span id="save-spinner-fs" class="ml-2 w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin hidden"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Cloner seulement le contenu du tableau (table)
|
||||||
|
const originalTable = tableContainer.querySelector('table');
|
||||||
|
const tableClone = originalTable.cloneNode(true);
|
||||||
|
const fsTableContainer = fullscreenOverlay.querySelector('.grading-table-container');
|
||||||
|
fsTableContainer.appendChild(tableClone);
|
||||||
|
|
||||||
|
// Synchroniser les valeurs actuelles du formulaire vers le clone
|
||||||
|
syncDataFromOriginalToFullscreen();
|
||||||
|
|
||||||
|
// Ajouter l'overlay au body
|
||||||
|
document.body.appendChild(fullscreenOverlay);
|
||||||
|
document.body.classList.add('fullscreen-mode');
|
||||||
|
|
||||||
|
// Masquer le conteneur original
|
||||||
|
tableContainer.style.display = 'none';
|
||||||
|
|
||||||
|
// Mettre à jour le bouton
|
||||||
|
btnText.textContent = 'Quitter';
|
||||||
|
btnIcon.setAttribute('d', 'M8 3v3a2 2 0 01-2 2H3m18 0h-3a2 2 0 01-2-2V3m0 18v-3a2 2 0 012-2h3M3 16h3a2 2 0 012 2v3');
|
||||||
|
btn.title = 'Quitter le plein écran';
|
||||||
|
btn.classList.remove('bg-purple-100', 'hover:bg-purple-200', 'text-purple-800');
|
||||||
|
btn.classList.add('bg-red-100', 'hover:bg-red-200', 'text-red-800');
|
||||||
|
|
||||||
|
// Réinitialiser les événements JavaScript sur la copie
|
||||||
|
setupFullscreenEvents();
|
||||||
|
|
||||||
|
isFullscreen = true;
|
||||||
|
} else {
|
||||||
|
// Supprimer l'overlay
|
||||||
|
if (fullscreenOverlay) {
|
||||||
|
document.body.removeChild(fullscreenOverlay);
|
||||||
|
fullscreenOverlay = null;
|
||||||
|
}
|
||||||
|
document.body.classList.remove('fullscreen-mode');
|
||||||
|
|
||||||
|
// Réafficher le conteneur original
|
||||||
|
const tableContainer = document.querySelector('.bg-white.shadow.rounded-lg');
|
||||||
|
tableContainer.style.display = '';
|
||||||
|
|
||||||
|
// Mettre à jour le bouton
|
||||||
|
btnText.textContent = 'Plein écran';
|
||||||
|
btnIcon.setAttribute('d', 'M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4');
|
||||||
|
btn.title = 'Passer en plein écran';
|
||||||
|
btn.classList.remove('bg-red-100', 'hover:bg-red-200', 'text-red-800');
|
||||||
|
btn.classList.add('bg-purple-100', 'hover:bg-purple-200', 'text-purple-800');
|
||||||
|
|
||||||
|
isFullscreen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupFullscreenEvents() {
|
||||||
|
if (!fullscreenOverlay) return;
|
||||||
|
|
||||||
|
// Réattacher les événements nécessaires
|
||||||
|
const filterInput = fullscreenOverlay.querySelector('#student-filter');
|
||||||
|
if (filterInput) {
|
||||||
|
setupStudentFilterForOverlay(filterInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchroniser l'indicateur de progression
|
||||||
|
const originalIndicator = document.getElementById('progress-indicator');
|
||||||
|
const fsIndicator = fullscreenOverlay.querySelector('#progress-indicator-fs');
|
||||||
|
if (originalIndicator && fsIndicator) {
|
||||||
|
fsIndicator.textContent = originalIndicator.textContent;
|
||||||
|
fsIndicator.className = originalIndicator.className;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Réattacher les événements sur les champs de saisie en mode plein écran
|
||||||
|
const fsInputs = fullscreenOverlay.querySelectorAll('.grading-input, .comment-input');
|
||||||
|
fsInputs.forEach(input => {
|
||||||
|
if (input.classList.contains('grading-input')) {
|
||||||
|
// Événements pour les champs de notation
|
||||||
|
input.addEventListener('input', function() { handleGradeChange(this); });
|
||||||
|
input.addEventListener('change', function() { handleGradeChange(this); });
|
||||||
|
input.addEventListener('focus', function() { handleGradeFocus(this); });
|
||||||
|
input.addEventListener('keydown', function(e) { handleGradeKeydown(e, this); });
|
||||||
|
} else if (input.classList.contains('comment-input')) {
|
||||||
|
// Événements pour les commentaires
|
||||||
|
input.addEventListener('keydown', function(e) { handleCommentKeydown(e, this); });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Appliquer les couleurs initiales aux champs pré-remplis
|
||||||
|
applyInitialColors();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupStudentFilterForOverlay(filterInput) {
|
||||||
|
// Réimplémentation simplifiée du filtre pour l'overlay
|
||||||
|
filterInput.addEventListener('input', function() {
|
||||||
|
const searchTerm = this.value.toLowerCase().trim();
|
||||||
|
const rows = fullscreenOverlay.querySelectorAll('.student-row');
|
||||||
|
let visibleCount = 0;
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const studentName = row.dataset.studentName;
|
||||||
|
const isVisible = !searchTerm || studentName.includes(searchTerm);
|
||||||
|
row.style.display = isVisible ? '' : 'none';
|
||||||
|
if (isVisible) visibleCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
const studentCount = fullscreenOverlay.querySelector('#student-count');
|
||||||
|
if (studentCount) {
|
||||||
|
studentCount.textContent = `${visibleCount} / ${rows.length} élève(s)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raccourci clavier pour le plein écran (F11 ou Échap pour quitter)
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'F11') {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleFullscreen();
|
||||||
|
} else if (e.key === 'Escape' && isFullscreen) {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleFullscreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Gestion de la fermeture de la page avec modifications non sauvegardées
|
// Gestion de la fermeture de la page avec modifications non sauvegardées
|
||||||
window.addEventListener('beforeunload', function(e) {
|
window.addEventListener('beforeunload', function(e) {
|
||||||
if (unsavedChanges.size > 0) {
|
if (unsavedChanges.size > 0) {
|
||||||
@@ -1039,4 +1351,4 @@ window.addEventListener('beforeunload', function(e) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user