feat: add mailing and bilan to send
This commit is contained in:
@@ -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 %}
|
||||
Reference in New Issue
Block a user