feat: add mailing and bilan to send

This commit is contained in:
2025-09-10 09:04:32 +02:00
parent 2c549c7234
commit 844d4d6bba
14 changed files with 2334 additions and 3 deletions

View File

@@ -52,6 +52,17 @@
</div>
</div>
<!-- Bouton d'envoi des bilans -->
<div class="flex justify-end mb-6">
<button id="sendReportsBtn" onclick="openSendReportsModal()"
class="inline-flex items-center px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
📧 Envoyer les bilans par email
</button>
</div>
<!-- Histogramme des notes -->
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
@@ -764,5 +775,284 @@ document.addEventListener('DOMContentLoaded', function() {
createGradingElementsHeatmap('gradingElementsHeatmap', gradingElementsData);
{% endif %}
});
// === FONCTIONNALITÉ D'ENVOI DE BILANS ===
let eligibleStudents = [];
let selectedStudents = [];
// Charger les élèves éligibles au chargement de la page
document.addEventListener('DOMContentLoaded', function() {
loadEligibleStudents();
});
function loadEligibleStudents() {
fetch(`/assessments/{{ assessment.id }}/eligible-students`)
.then(response => response.json())
.then(data => {
if (data.success) {
eligibleStudents = data.students;
updateSendReportsButton(data.with_email_count, data.total_count);
} else {
console.error('Erreur lors du chargement des élèves:', data.error);
}
})
.catch(error => {
console.error('Erreur réseau:', error);
});
}
function updateSendReportsButton(withEmailCount, totalCount) {
const btn = document.getElementById('sendReportsBtn');
if (withEmailCount === 0) {
btn.disabled = true;
btn.classList.add('opacity-50', 'cursor-not-allowed');
btn.classList.remove('hover:bg-blue-700');
btn.innerHTML = `
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Aucune adresse email configurée
`;
} else {
btn.innerHTML = `
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
📧 Envoyer les bilans (${withEmailCount}/${totalCount} avec email)
`;
}
}
function openSendReportsModal() {
if (eligibleStudents.length === 0) {
alert('Aucun élève éligible trouvé');
return;
}
document.getElementById('sendReportsModal').classList.remove('hidden');
populateStudentsList();
}
function closeSendReportsModal() {
document.getElementById('sendReportsModal').classList.add('hidden');
selectedStudents = [];
}
function populateStudentsList() {
const container = document.getElementById('studentsList');
const studentsWithEmail = eligibleStudents.filter(s => s.has_email);
if (studentsWithEmail.length === 0) {
container.innerHTML = '<p class="text-gray-500 text-center py-4">Aucun élève avec adresse email configurée</p>';
return;
}
container.innerHTML = studentsWithEmail.map(student => `
<label class="flex items-center p-3 hover:bg-gray-50 rounded-lg cursor-pointer">
<input type="checkbox"
value="${student.id}"
onchange="updateSelectedStudents()"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-3">
<div class="flex-1">
<div class="font-medium text-gray-900">${student.full_name}</div>
<div class="text-sm text-gray-500">${student.email}</div>
</div>
</label>
`).join('');
// Sélectionner tous par défaut
selectAllStudents();
}
function selectAllStudents() {
const checkboxes = document.querySelectorAll('#studentsList input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = true);
updateSelectedStudents();
}
function unselectAllStudents() {
const checkboxes = document.querySelectorAll('#studentsList input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = false);
updateSelectedStudents();
}
function updateSelectedStudents() {
const checkboxes = document.querySelectorAll('#studentsList input[type="checkbox"]:checked');
selectedStudents = Array.from(checkboxes).map(cb => parseInt(cb.value));
const count = selectedStudents.length;
document.getElementById('selectedCount').textContent = count;
document.getElementById('sendReportsSubmitBtn').disabled = count === 0;
}
function sendReports() {
if (selectedStudents.length === 0) {
alert('Veuillez sélectionner au moins un élève');
return;
}
const customMessage = document.getElementById('customMessage').value.trim();
const submitBtn = document.getElementById('sendReportsSubmitBtn');
const originalText = submitBtn.innerHTML;
// Désactiver le bouton et afficher le loading
submitBtn.disabled = true;
submitBtn.innerHTML = `
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Envoi en cours...
`;
// Envoyer la requête
fetch(`/assessments/{{ assessment.id }}/send-reports`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
student_ids: selectedStudents,
custom_message: customMessage
})
})
.then(response => response.json())
.then(data => {
// Restaurer le bouton
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
if (data.success) {
// Afficher le résultat
showSendResult(data);
// Fermer la modal après un délai
setTimeout(() => {
closeSendReportsModal();
}, 3000);
} else {
alert(`Erreur lors de l'envoi: ${data.error}`);
}
})
.catch(error => {
// Restaurer le bouton en cas d'erreur
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
console.error('Erreur:', error);
alert('Erreur réseau lors de l\'envoi des bilans');
});
}
function showSendResult(result) {
const resultDiv = document.getElementById('sendResult');
let content = `<div class="mb-4">
<h3 class="text-lg font-medium ${result.success ? 'text-green-600' : 'text-red-600'} mb-2">
${result.message}
</h3>
</div>`;
if (result.sent_count > 0) {
content += `<div class="mb-3">
<p class="text-green-600">✅ ${result.sent_count} bilan(s) envoyé(s) avec succès</p>
</div>`;
}
if (result.error_count > 0) {
content += `<div class="mb-3">
<p class="text-red-600 font-medium">❌ ${result.error_count} erreur(s):</p>
<ul class="text-sm text-red-600 mt-2 max-h-32 overflow-y-auto">`;
result.errors.forEach(error => {
content += `<li class="py-1">• ${error}</li>`;
});
content += `</ul></div>`;
}
resultDiv.innerHTML = content;
resultDiv.classList.remove('hidden');
// Cacher les autres sections
document.getElementById('studentsSelection').classList.add('hidden');
document.getElementById('modalActions').classList.add('hidden');
}
// Fermer la modal en cliquant à côté
document.addEventListener('click', function(e) {
const modal = document.getElementById('sendReportsModal');
if (e.target === modal) {
closeSendReportsModal();
}
});
</script>
<!-- Modal d'envoi des bilans -->
<div id="sendReportsModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div class="relative top-20 mx-auto p-5 border w-11/12 max-w-2xl shadow-lg rounded-md bg-white">
<div class="mt-3">
<!-- En-tête -->
<div class="flex justify-between items-center pb-4 border-b">
<h3 class="text-lg font-medium text-gray-900">📧 Envoyer les bilans par email</h3>
<button onclick="closeSendReportsModal()" class="text-gray-400 hover:text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<!-- Sélection des élèves -->
<div id="studentsSelection" class="py-4">
<div class="flex justify-between items-center mb-4">
<h4 class="font-medium text-gray-900">Sélectionner les élèves</h4>
<div class="flex space-x-2">
<button onclick="selectAllStudents()"
class="text-sm text-blue-600 hover:text-blue-800">Tout sélectionner</button>
<span class="text-gray-300">|</span>
<button onclick="unselectAllStudents()"
class="text-sm text-blue-600 hover:text-blue-800">Tout désélectionner</button>
</div>
</div>
<div id="studentsList" class="max-h-64 overflow-y-auto border border-gray-200 rounded-lg p-2 space-y-1">
<!-- Les élèves seront ajoutés ici par JavaScript -->
</div>
<p class="text-sm text-gray-600 mt-2">
<span id="selectedCount">0</span> élève(s) sélectionné(s)
</p>
</div>
<!-- Message personnalisé -->
<div class="py-4 border-t">
<label for="customMessage" class="block text-sm font-medium text-gray-700 mb-2">
Message personnalisé (optionnel)
</label>
<textarea id="customMessage"
rows="3"
placeholder="Message du professeur à ajouter dans les bilans..."
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
</div>
<!-- Résultat de l'envoi -->
<div id="sendResult" class="hidden py-4 border-t">
<!-- Le résultat sera affiché ici -->
</div>
<!-- Actions -->
<div id="modalActions" class="flex justify-end space-x-3 pt-4 border-t">
<button onclick="closeSendReportsModal()"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50">
Annuler
</button>
<button id="sendReportsSubmitBtn" onclick="sendReports()"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
disabled>
📧 Envoyer les bilans
</button>
</div>
</div>
</div>
</div>
{% endblock %}