|
|
|
@ -7,69 +7,87 @@ |
|
|
|
<link href="https://cdn.jsdelivr.net/npm/startbootstrap-sb-admin@7.0.6/dist/css/styles.min.css" rel="stylesheet"> |
|
|
|
<link href="https://cdn.jsdelivr.net/npm/startbootstrap-sb-admin@7.0.6/dist/css/styles.min.css" rel="stylesheet"> |
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> |
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> |
|
|
|
<style> |
|
|
|
<style> |
|
|
|
:root { |
|
|
|
:root { |
|
|
|
color-scheme: light dark; |
|
|
|
color-scheme: light dark; |
|
|
|
} |
|
|
|
} |
|
|
|
body, .bg-light { |
|
|
|
body, .bg-light { |
|
|
|
background-color: #f8f9fa; |
|
|
|
background-color: #f8f9fa; |
|
|
|
color: #212529; |
|
|
|
color: #212529; |
|
|
|
} |
|
|
|
} |
|
|
|
@media (prefers-color-scheme: dark) { |
|
|
|
@media (prefers-color-scheme: dark) { |
|
|
|
html, body, .bg-light { |
|
|
|
html, body, .bg-light { |
|
|
|
background-color: #181a1b !important; |
|
|
|
background-color: #181a1b !important; |
|
|
|
color: #f8f9fa !important; |
|
|
|
color: #f8f9fa !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.card, .card-header, .card-body { |
|
|
|
.card, .card-header, .card-body { |
|
|
|
background-color: #23272b !important; |
|
|
|
background-color: #23272b !important; |
|
|
|
color: #f8f9fa !important; |
|
|
|
color: #f8f9fa !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.table, .table-bordered { |
|
|
|
.table, .table-bordered { |
|
|
|
color: #f8f9fa; |
|
|
|
color: #f8f9fa; |
|
|
|
background-color: #23272b !important; |
|
|
|
background-color: #23272b !important; |
|
|
|
border-color: #444c56 !important; |
|
|
|
border-color: #444c56 !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.table-bordered th, .table-bordered td { |
|
|
|
.table-bordered th, .table-bordered td { |
|
|
|
border-color: #444c56 !important; |
|
|
|
border-color: #444c56 !important; |
|
|
|
background-color: #23272b !important; |
|
|
|
background-color: #23272b !important; |
|
|
|
color: #f8f9fa !important; |
|
|
|
color: #f8f9fa !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.table-striped > tbody > tr:nth-of-type(odd) { |
|
|
|
.table-striped > tbody > tr:nth-of-type(odd) { |
|
|
|
background-color: #20232a !important; |
|
|
|
background-color: #20232a !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.form-select, .form-label, .btn, .alert { |
|
|
|
.form-select, .form-label, .btn, .alert { |
|
|
|
background-color: #23272b !important; |
|
|
|
background-color: #23272b !important; |
|
|
|
color: #f8f9fa !important; |
|
|
|
color: #f8f9fa !important; |
|
|
|
border-color: #444c56 !important; |
|
|
|
border-color: #444c56 !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.btn-primary { |
|
|
|
.btn-primary { |
|
|
|
background-color: #375a7f !important; |
|
|
|
background-color: #375a7f !important; |
|
|
|
border-color: #375a7f !important; |
|
|
|
border-color: #375a7f !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.btn-danger { |
|
|
|
.btn-danger { |
|
|
|
background-color: #c9302c !important; |
|
|
|
background-color: #c9302c !important; |
|
|
|
border-color: #c9302c !important; |
|
|
|
border-color: #c9302c !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.btn-success { |
|
|
|
.btn-success { |
|
|
|
background-color: #449d44 !important; |
|
|
|
background-color: #449d44 !important; |
|
|
|
border-color: #449d44 !important; |
|
|
|
border-color: #449d44 !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.navbar, .navbar-dark, .bg-dark { |
|
|
|
.navbar, .navbar-dark, .bg-dark { |
|
|
|
background-color: #23272b !important; |
|
|
|
background-color: #23272b !important; |
|
|
|
} |
|
|
|
} |
|
|
|
.alert-warning { |
|
|
|
.alert-warning { |
|
|
|
background-color: #3a3a3a !important; |
|
|
|
background-color: #3a3a3a !important; |
|
|
|
color: #ffe082 !important; |
|
|
|
color: #ffe082 !important; |
|
|
|
border-color: #444c56 !important; |
|
|
|
border-color: #444c56 !important; |
|
|
|
} |
|
|
|
} |
|
|
|
[class*="bg-"] { |
|
|
|
[class*="bg-"] { |
|
|
|
background-color: inherit !important; |
|
|
|
background-color: inherit !important; |
|
|
|
} |
|
|
|
} |
|
|
|
#credit-restant { |
|
|
|
#credit-restant { |
|
|
|
background-color: #ffe082 !important; |
|
|
|
background-color: #ffe082 !important; |
|
|
|
color: #23272b !important; |
|
|
|
color: #23272b !important; |
|
|
|
border: 1px solid #444c56 !important; |
|
|
|
border: 1px solid #444c56 !important; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
.btn-outline-primary, .btn-outline-primary:focus, .btn-outline-primary:active, .btn-outline-primary:hover { |
|
|
|
|
|
|
|
color: #23272b !important; |
|
|
|
|
|
|
|
background-color: #ffe082 !important; |
|
|
|
|
|
|
|
border-color: #ffe082 !important; |
|
|
|
|
|
|
|
box-shadow: none !important; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.btn-outline-primary:disabled, .btn-outline-primary.disabled { |
|
|
|
|
|
|
|
color: #bdbdbd !important; |
|
|
|
|
|
|
|
background-color: #444c56 !important; |
|
|
|
|
|
|
|
border-color: #444c56 !important; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#bulletinModal .modal-content, #bulletinModal .modal-body, #bulletinModal .modal-header { |
|
|
|
|
|
|
|
background-color: #fff !important; |
|
|
|
|
|
|
|
color: #23272b !important; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#bulletinModal .modal-title { |
|
|
|
|
|
|
|
color: #23272b !important; |
|
|
|
|
|
|
|
} |
|
|
|
</style> |
|
|
|
</style> |
|
|
|
</head> |
|
|
|
</head> |
|
|
|
<body class="bg-light"> |
|
|
|
<body class="bg-light"> |
|
|
|
@ -108,7 +126,7 @@ body, .bg-light { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="mb-3 d-flex gap-2"> |
|
|
|
<div class="mb-3 d-flex gap-2"> |
|
|
|
<button id="generation-toggle" class="btn btn-primary mb-3">Lancer la génération</button> |
|
|
|
<button id="generation-toggle" class="btn btn-primary mb-3">Lancer la génération</button> |
|
|
|
<button id="export-pdf" class="btn btn-secondary mb-3" type="button" onclick="exportTableToPDF()">Exporter en PDF</button> |
|
|
|
<button id="export-pdf" class="btn btn-primary mb-3" type="button" onclick="exportTableToPDF()">Exporter en PDF</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div id="credit-alert" class="alert alert-danger d-none" role="alert"> |
|
|
|
<div id="credit-alert" class="alert alert-danger d-none" role="alert"> |
|
|
|
Vous n'avez plus de crédits pour générer des appréciations. |
|
|
|
Vous n'avez plus de crédits pour générer des appréciations. |
|
|
|
@ -122,219 +140,301 @@ body, .bg-light { |
|
|
|
</tr> |
|
|
|
</tr> |
|
|
|
</thead> |
|
|
|
</thead> |
|
|
|
<tbody> |
|
|
|
<tbody> |
|
|
|
{% if appreciations_json and appreciations_json|length > 0 %} |
|
|
|
{% if appreciations_json and appreciations_json|length > 0 %} |
|
|
|
{% for appreciation in appreciations_json %} |
|
|
|
{% for appreciation in appreciations_json %} |
|
|
|
|
|
|
|
<tr> |
|
|
|
|
|
|
|
<td class="eleve" style="width:1%; white-space:nowrap;">{{ appreciation.eleve }}</td> |
|
|
|
|
|
|
|
<td class="appreciation"></td> |
|
|
|
|
|
|
|
<td style="width:1%; text-align:center; vertical-align:middle;"> |
|
|
|
|
|
|
|
<div class="d-flex justify-content-center gap-2"> |
|
|
|
|
|
|
|
<button class="btn btn-outline-primary btn-sm action-generate w-100" type="button">Générer</button> |
|
|
|
|
|
|
|
<button class="btn btn-outline-primary btn-sm action-bulletin w-100" type="button" data-eleve="{{ appreciation.eleve }}">Bulletin</button> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</td> |
|
|
|
|
|
|
|
</tr> |
|
|
|
|
|
|
|
{% endfor %} |
|
|
|
|
|
|
|
{% else %} |
|
|
|
<tr> |
|
|
|
<tr> |
|
|
|
<td class="eleve" style="width:1%; white-space:nowrap;">{{ appreciation.eleve }}</td> |
|
|
|
<td colspan="3"><div class="alert alert-warning mb-0">Aucune appréciation générée.</div></td> |
|
|
|
<td class="appreciation"></td> |
|
|
|
|
|
|
|
<td style="width:1%; text-align:center;"><button class="btn btn-outline-primary btn-sm action-generate" type="button">Générer</button></td> |
|
|
|
|
|
|
|
</tr> |
|
|
|
</tr> |
|
|
|
{% endfor %} |
|
|
|
{% endif %} |
|
|
|
{% else %} |
|
|
|
|
|
|
|
<tr> |
|
|
|
|
|
|
|
<td colspan="3"><div class="alert alert-warning mb-0">Aucune appréciation générée.</div></td> |
|
|
|
|
|
|
|
</tr> |
|
|
|
|
|
|
|
{% endif %} |
|
|
|
|
|
|
|
</tbody> |
|
|
|
</tbody> |
|
|
|
</table> |
|
|
|
</table> |
|
|
|
|
|
|
|
<!-- Injection des appréciations dans une variable JS globale pour accès rapide --> |
|
|
|
|
|
|
|
<script id="appreciations-data" type="application/json"> |
|
|
|
|
|
|
|
{{ appreciations_json|safe }} |
|
|
|
|
|
|
|
</script> |
|
|
|
<script> |
|
|
|
<script> |
|
|
|
let stopGeneration = false; |
|
|
|
window.allAppreciations = {}; |
|
|
|
let enCours = false; |
|
|
|
try { |
|
|
|
|
|
|
|
const appreciationsList = JSON.parse(document.getElementById('appreciations-data').textContent); |
|
|
|
|
|
|
|
appreciationsList.forEach(eleve => { |
|
|
|
|
|
|
|
let appreciations = eleve.appreciations; |
|
|
|
|
|
|
|
if (typeof appreciations === 'string') { |
|
|
|
|
|
|
|
try { appreciations = JSON.parse(appreciations); } catch (e) { appreciations = []; } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
window.allAppreciations[eleve.eleve] = appreciations; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} catch (e) { window.allAppreciations = {}; } |
|
|
|
|
|
|
|
</script> |
|
|
|
|
|
|
|
<script> |
|
|
|
|
|
|
|
let stopGeneration = false; |
|
|
|
|
|
|
|
let enCours = false; |
|
|
|
|
|
|
|
|
|
|
|
function updateActionButtons() { |
|
|
|
function updateActionButtons() { |
|
|
|
const rows = document.querySelectorAll('#appreciations-table tbody tr'); |
|
|
|
const rows = document.querySelectorAll('#appreciations-table tbody tr'); |
|
|
|
rows.forEach(row => { |
|
|
|
rows.forEach(row => { |
|
|
|
const appreciationCell = row.querySelector('.appreciation'); |
|
|
|
const appreciationCell = row.querySelector('.appreciation'); |
|
|
|
const btn = row.querySelector('.action-generate'); |
|
|
|
const btn = row.querySelector('.action-generate'); |
|
|
|
if (!btn) return; |
|
|
|
if (!btn) return; |
|
|
|
if (appreciationCell.textContent.trim()) { |
|
|
|
if (appreciationCell.textContent.trim()) { |
|
|
|
btn.textContent = 'Régénérer'; |
|
|
|
btn.textContent = 'Régénérer'; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
btn.textContent = 'Générer'; |
|
|
|
btn.textContent = 'Générer'; |
|
|
|
} |
|
|
|
} |
|
|
|
btn.disabled = enCours; |
|
|
|
btn.disabled = enCours; |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function getRowsSansAppreciation() { |
|
|
|
function getRowsSansAppreciation() { |
|
|
|
return Array.from(document.querySelectorAll('#appreciations-table tbody tr')).filter(row => !row.querySelector('.appreciation').textContent.trim()); |
|
|
|
return Array.from(document.querySelectorAll('#appreciations-table tbody tr')).filter(row => !row.querySelector('.appreciation').textContent.trim()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
const btnToggle = document.getElementById('generation-toggle'); |
|
|
|
const btnToggle = document.getElementById('generation-toggle'); |
|
|
|
const selectModele = document.getElementById('modele-select'); |
|
|
|
const selectModele = document.getElementById('modele-select'); |
|
|
|
const creditRestant = document.getElementById('credit-restant'); |
|
|
|
const creditRestant = document.getElementById('credit-restant'); |
|
|
|
const creditAlert = document.getElementById('credit-alert'); |
|
|
|
const creditAlert = document.getElementById('credit-alert'); |
|
|
|
let rows = getRowsSansAppreciation(); |
|
|
|
let rows = getRowsSansAppreciation(); |
|
|
|
|
|
|
|
|
|
|
|
function setButtonState(state) { |
|
|
|
function setButtonState(state) { |
|
|
|
if (state === 'lancer') { |
|
|
|
if (state === 'lancer') { |
|
|
|
btnToggle.textContent = 'Lancer la génération'; |
|
|
|
btnToggle.textContent = 'Lancer la génération'; |
|
|
|
btnToggle.className = 'btn btn-primary mb-3'; |
|
|
|
btnToggle.className = 'btn btn-primary mb-3'; |
|
|
|
} else if (state === 'arreter') { |
|
|
|
} else if (state === 'arreter') { |
|
|
|
btnToggle.textContent = 'Arrêter la génération'; |
|
|
|
btnToggle.textContent = 'Arrêter la génération'; |
|
|
|
btnToggle.className = 'btn btn-danger mb-3'; |
|
|
|
btnToggle.className = 'btn btn-danger mb-3'; |
|
|
|
} else if (state === 'continuer') { |
|
|
|
} else if (state === 'continuer') { |
|
|
|
btnToggle.textContent = 'Continuer la génération'; |
|
|
|
btnToggle.textContent = 'Continuer la génération'; |
|
|
|
btnToggle.className = 'btn btn-success mb-3'; |
|
|
|
btnToggle.className = 'btn btn-success mb-3'; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function traiterLignes() { |
|
|
|
function traiterLignes() { |
|
|
|
rows = getRowsSansAppreciation(); |
|
|
|
rows = getRowsSansAppreciation(); |
|
|
|
updateActionButtons(); |
|
|
|
updateActionButtons(); |
|
|
|
if (rows.length === 0 || stopGeneration) { |
|
|
|
if (rows.length === 0 || stopGeneration) { |
|
|
|
enCours = false; |
|
|
|
enCours = false; |
|
|
|
updateActionButtons(); |
|
|
|
updateActionButtons(); |
|
|
|
if (stopGeneration) setButtonState('continuer'); |
|
|
|
if (stopGeneration) setButtonState('continuer'); |
|
|
|
else setButtonState('lancer'); |
|
|
|
else setButtonState('lancer'); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
enCours = true; |
|
|
|
enCours = true; |
|
|
|
updateActionButtons(); |
|
|
|
updateActionButtons(); |
|
|
|
setButtonState('arreter'); |
|
|
|
setButtonState('arreter'); |
|
|
|
const row = rows[0]; |
|
|
|
const row = rows[0]; |
|
|
|
const eleve = row.querySelector('.eleve').textContent; |
|
|
|
const eleve = row.querySelector('.eleve').textContent; |
|
|
|
const appreciationCell = row.querySelector('.appreciation'); |
|
|
|
const appreciationCell = row.querySelector('.appreciation'); |
|
|
|
appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...'; |
|
|
|
appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...'; |
|
|
|
fetch("{% url 'generer_appreciation_ajax' %}", { |
|
|
|
fetch("{% url 'generer_appreciation_ajax' %}", { |
|
|
|
method: 'POST', |
|
|
|
method: 'POST', |
|
|
|
headers: { |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/json', |
|
|
|
'Content-Type': 'application/json', |
|
|
|
'X-CSRFToken': '{{ csrf_token }}' |
|
|
|
'X-CSRFToken': '{{ csrf_token }}' |
|
|
|
}, |
|
|
|
}, |
|
|
|
body: JSON.stringify({eleve: eleve, modele: selectModele.value}) |
|
|
|
body: JSON.stringify({eleve: eleve, modele: selectModele.value}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
.then(response => response.json()) |
|
|
|
.then(response => response.json()) |
|
|
|
.then(data => { |
|
|
|
.then(data => { |
|
|
|
appreciationCell.textContent = data.appreciation; |
|
|
|
appreciationCell.textContent = data.appreciation; |
|
|
|
updateActionButtons(); |
|
|
|
updateActionButtons(); |
|
|
|
if (typeof data.credit !== 'undefined') { |
|
|
|
if (typeof data.credit !== 'undefined') { |
|
|
|
creditRestant.textContent = 'Crédits restants : ' + data.credit; |
|
|
|
creditRestant.textContent = 'Crédits restants : ' + data.credit; |
|
|
|
} |
|
|
|
} |
|
|
|
if (data.stop) { |
|
|
|
if (data.stop) { |
|
|
|
stopGeneration = true; |
|
|
|
stopGeneration = true; |
|
|
|
setButtonState('lancer'); |
|
|
|
setButtonState('lancer'); |
|
|
|
if (data.credit === 0) { |
|
|
|
if (data.credit === 0) { |
|
|
|
creditAlert.classList.remove('d-none'); |
|
|
|
creditAlert.classList.remove('d-none'); |
|
|
|
} |
|
|
|
} |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
traiterLignes(); |
|
|
|
traiterLignes(); |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch(() => { |
|
|
|
.catch(() => { |
|
|
|
appreciationCell.textContent = 'Erreur lors de la génération'; |
|
|
|
appreciationCell.textContent = 'Erreur lors de la génération'; |
|
|
|
updateActionButtons(); |
|
|
|
updateActionButtons(); |
|
|
|
traiterLignes(); |
|
|
|
traiterLignes(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
btnToggle.addEventListener('click', function() { |
|
|
|
btnToggle.addEventListener('click', function() { |
|
|
|
const credit = parseInt(creditRestant.textContent.replace(/\D/g, '')); |
|
|
|
const credit = parseInt(creditRestant.textContent.replace(/\D/g, '')); |
|
|
|
if (credit === 0) { |
|
|
|
if (credit === 0) { |
|
|
|
creditAlert.classList.remove('d-none'); |
|
|
|
creditAlert.classList.remove('d-none'); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
creditAlert.classList.add('d-none'); |
|
|
|
creditAlert.classList.add('d-none'); |
|
|
|
} |
|
|
|
} |
|
|
|
if (!enCours && (btnToggle.textContent === 'Lancer la génération' || btnToggle.textContent === 'Continuer la génération')) { |
|
|
|
if (!enCours && (btnToggle.textContent === 'Lancer la génération' || btnToggle.textContent === 'Continuer la génération')) { |
|
|
|
stopGeneration = false; |
|
|
|
stopGeneration = false; |
|
|
|
setButtonState('arreter'); |
|
|
|
setButtonState('arreter'); |
|
|
|
traiterLignes(); |
|
|
|
traiterLignes(); |
|
|
|
} else if (enCours && btnToggle.textContent === 'Arrêter la génération') { |
|
|
|
} else if (enCours && btnToggle.textContent === 'Arrêter la génération') { |
|
|
|
stopGeneration = true; |
|
|
|
stopGeneration = true; |
|
|
|
setButtonState('continuer'); |
|
|
|
setButtonState('continuer'); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// Action individuelle sur bouton Générer/Régénérer |
|
|
|
// Action individuelle sur bouton Générer/Régénérer |
|
|
|
document.querySelectorAll('.action-generate').forEach(btn => { |
|
|
|
document.querySelectorAll('.action-generate').forEach(btn => { |
|
|
|
btn.addEventListener('click', function() { |
|
|
|
btn.addEventListener('click', function() { |
|
|
|
if (enCours) return; |
|
|
|
if (enCours) return; |
|
|
|
const row = btn.closest('tr'); |
|
|
|
const row = btn.closest('tr'); |
|
|
|
const eleve = row.querySelector('.eleve').textContent; |
|
|
|
const eleve = row.querySelector('.eleve').textContent; |
|
|
|
const appreciationCell = row.querySelector('.appreciation'); |
|
|
|
const appreciationCell = row.querySelector('.appreciation'); |
|
|
|
appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...'; |
|
|
|
appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...'; |
|
|
|
btn.disabled = true; |
|
|
|
btn.disabled = true; |
|
|
|
fetch("{% url 'generer_appreciation_ajax' %}", { |
|
|
|
fetch("{% url 'generer_appreciation_ajax' %}", { |
|
|
|
method: 'POST', |
|
|
|
method: 'POST', |
|
|
|
headers: { |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/json', |
|
|
|
'Content-Type': 'application/json', |
|
|
|
'X-CSRFToken': '{{ csrf_token }}' |
|
|
|
'X-CSRFToken': '{{ csrf_token }}' |
|
|
|
}, |
|
|
|
}, |
|
|
|
body: JSON.stringify({eleve: eleve, modele: selectModele.value}) |
|
|
|
body: JSON.stringify({eleve: eleve, modele: selectModele.value}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
.then(response => response.json()) |
|
|
|
.then(response => response.json()) |
|
|
|
.then(data => { |
|
|
|
.then(data => { |
|
|
|
appreciationCell.textContent = data.appreciation; |
|
|
|
appreciationCell.textContent = data.appreciation; |
|
|
|
updateActionButtons(); |
|
|
|
updateActionButtons(); |
|
|
|
if (typeof data.credit !== 'undefined') { |
|
|
|
if (typeof data.credit !== 'undefined') { |
|
|
|
creditRestant.textContent = 'Crédits restants : ' + data.credit; |
|
|
|
creditRestant.textContent = 'Crédits restants : ' + data.credit; |
|
|
|
} |
|
|
|
} |
|
|
|
if (data.credit === 0) { |
|
|
|
if (data.credit === 0) { |
|
|
|
creditAlert.classList.remove('d-none'); |
|
|
|
creditAlert.classList.remove('d-none'); |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch(() => { |
|
|
|
.catch(() => { |
|
|
|
appreciationCell.textContent = 'Erreur lors de la génération'; |
|
|
|
appreciationCell.textContent = 'Erreur lors de la génération'; |
|
|
|
updateActionButtons(); |
|
|
|
updateActionButtons(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
setButtonState('lancer'); |
|
|
|
// Action individuelle sur bouton Bulletin |
|
|
|
updateActionButtons(); |
|
|
|
document.querySelectorAll('.action-bulletin').forEach(btn => { |
|
|
|
}); |
|
|
|
btn.addEventListener('click', function() { |
|
|
|
</script> |
|
|
|
const eleve = btn.getAttribute('data-eleve'); |
|
|
|
|
|
|
|
fetch("{% url 'get_bulletin_eleve' %}", { |
|
|
|
|
|
|
|
method: 'POST', |
|
|
|
|
|
|
|
headers: { |
|
|
|
|
|
|
|
'Content-Type': 'application/json', |
|
|
|
|
|
|
|
'X-CSRFToken': '{{ csrf_token }}' |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
body: JSON.stringify({eleve: eleve}) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.then(response => response.json()) |
|
|
|
|
|
|
|
.then(data => { |
|
|
|
|
|
|
|
// Mettre le titre dans le header du modal |
|
|
|
|
|
|
|
const modalTitle = document.getElementById('bulletinModalLabel'); |
|
|
|
|
|
|
|
if (modalTitle) { |
|
|
|
|
|
|
|
modalTitle.textContent = `Bulletin de l'élève ${eleve}`; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Mettre uniquement les appréciations dans le body |
|
|
|
|
|
|
|
let message = ''; |
|
|
|
|
|
|
|
if (Array.isArray(data.appreciations) && data.appreciations.length > 0) { |
|
|
|
|
|
|
|
message += data.appreciations.map(app => `<div style='margin-bottom:8px;'><strong>${app.matiere} :</strong><br>${app.appreciation}</div>`).join(''); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
message += '<em>Aucune appréciation trouvée dans le bulletin.</em>'; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
showBulletinModal(message); |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch(() => { |
|
|
|
|
|
|
|
const modalTitle = document.getElementById('bulletinModalLabel'); |
|
|
|
|
|
|
|
if (modalTitle) { |
|
|
|
|
|
|
|
modalTitle.textContent = `Bulletin`; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
showBulletinModal('<em>Erreur lors de la récupération du bulletin.</em>'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setButtonState('lancer'); |
|
|
|
|
|
|
|
updateActionButtons(); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
</script> |
|
|
|
<script> |
|
|
|
<script> |
|
|
|
// CDN jsPDF + autoTable pour export PDF |
|
|
|
// CDN jsPDF + autoTable pour export PDF |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
if (!window.jspdfLoaded) { |
|
|
|
if (!window.jspdfLoaded) { |
|
|
|
const script1 = document.createElement('script'); |
|
|
|
const script1 = document.createElement('script'); |
|
|
|
script1.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; |
|
|
|
script1.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; |
|
|
|
script1.onload = function() { |
|
|
|
script1.onload = function() { |
|
|
|
const script2 = document.createElement('script'); |
|
|
|
const script2 = document.createElement('script'); |
|
|
|
script2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js'; |
|
|
|
script2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js'; |
|
|
|
script2.onload = function() { window.jspdfLoaded = true; }; |
|
|
|
script2.onload = function() { window.jspdfLoaded = true; }; |
|
|
|
document.body.appendChild(script2); |
|
|
|
document.body.appendChild(script2); |
|
|
|
}; |
|
|
|
}; |
|
|
|
document.body.appendChild(script1); |
|
|
|
document.body.appendChild(script1); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
function exportTableToPDF() { |
|
|
|
function exportTableToPDF() { |
|
|
|
const doc = new window.jspdf.jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); |
|
|
|
const doc = new window.jspdf.jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); |
|
|
|
doc.text('Tableau des appréciations', 40, 40); |
|
|
|
doc.text('Tableau des appréciations', 40, 40); |
|
|
|
// Couleurs fixes mode clair |
|
|
|
// Couleurs fixes mode clair |
|
|
|
const headColor = [55, 90, 127]; |
|
|
|
const headColor = [55, 90, 127]; |
|
|
|
const textColor = [33, 37, 41]; |
|
|
|
const textColor = [33, 37, 41]; |
|
|
|
const bgColor = [255, 255, 255]; |
|
|
|
const bgColor = [255, 255, 255]; |
|
|
|
const borderColor = [200, 200, 200]; |
|
|
|
const borderColor = [200, 200, 200]; |
|
|
|
const table = document.getElementById('appreciations-table'); |
|
|
|
const table = document.getElementById('appreciations-table'); |
|
|
|
const rows = Array.from(table.querySelectorAll('tbody tr')) |
|
|
|
const rows = Array.from(table.querySelectorAll('tbody tr')) |
|
|
|
.map(tr => [ |
|
|
|
.map(tr => [ |
|
|
|
tr.querySelector('.eleve')?.textContent.trim() || '', |
|
|
|
tr.querySelector('.eleve')?.textContent.trim() || '', |
|
|
|
tr.querySelector('.appreciation')?.textContent.trim() || '' |
|
|
|
tr.querySelector('.appreciation')?.textContent.trim() || '' |
|
|
|
]); |
|
|
|
]); |
|
|
|
doc.autoTable({ |
|
|
|
doc.autoTable({ |
|
|
|
head: [['Élève', 'Appréciation']], |
|
|
|
head: [['Élève', 'Appréciation']], |
|
|
|
body: rows, |
|
|
|
body: rows, |
|
|
|
startY: 60, |
|
|
|
startY: 60, |
|
|
|
margin: { left: 30, right: 30 }, |
|
|
|
margin: { left: 30, right: 30 }, |
|
|
|
styles: { fontSize: 11, overflow: 'linebreak', cellWidth: 'auto', textColor: textColor, fillColor: bgColor, lineColor: borderColor }, |
|
|
|
styles: { fontSize: 11, overflow: 'linebreak', cellWidth: 'auto', textColor: textColor, fillColor: bgColor, lineColor: borderColor }, |
|
|
|
headStyles: { fillColor: headColor, textColor: textColor, lineColor: borderColor }, |
|
|
|
headStyles: { fillColor: headColor, textColor: textColor, lineColor: borderColor }, |
|
|
|
bodyStyles: { fillColor: bgColor, textColor: textColor, lineColor: borderColor }, |
|
|
|
bodyStyles: { fillColor: bgColor, textColor: textColor, lineColor: borderColor }, |
|
|
|
alternateRowStyles: { fillColor: [245, 245, 245] }, |
|
|
|
alternateRowStyles: { fillColor: [245, 245, 245] }, |
|
|
|
columnStyles: { |
|
|
|
columnStyles: { |
|
|
|
0: { cellWidth: 'auto'}, |
|
|
|
0: { cellWidth: 'auto'}, |
|
|
|
1: { cellWidth: 'auto' } |
|
|
|
1: { cellWidth: 'auto' } |
|
|
|
}, |
|
|
|
}, |
|
|
|
tableWidth: 'auto', |
|
|
|
tableWidth: 'auto', |
|
|
|
pageBreak: 'auto', |
|
|
|
pageBreak: 'auto', |
|
|
|
}); |
|
|
|
}); |
|
|
|
doc.save('appreciations.pdf'); |
|
|
|
doc.save('appreciations.pdf'); |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
</script> |
|
|
|
|
|
|
|
<script> |
|
|
|
|
|
|
|
function showBulletinModal(html) { |
|
|
|
|
|
|
|
const modalBody = document.getElementById('bulletinModalBody'); |
|
|
|
|
|
|
|
if (modalBody) { |
|
|
|
|
|
|
|
modalBody.innerHTML = html; |
|
|
|
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('bulletinModal')); |
|
|
|
|
|
|
|
modal.show(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
</script> |
|
|
|
|
|
|
|
<!-- Modal Bulletin --> |
|
|
|
|
|
|
|
<div class="modal fade" id="bulletinModal" tabindex="-1" aria-labelledby="bulletinModalLabel" aria-hidden="true"> |
|
|
|
|
|
|
|
<div class="modal-dialog modal-dialog-centered" style="max-width:80vw; width:80vw;"> |
|
|
|
|
|
|
|
<div class="modal-content"> |
|
|
|
|
|
|
|
<div class="modal-header"> |
|
|
|
|
|
|
|
<h5 class="modal-title" id="bulletinModalLabel">Bulletin</h5> |
|
|
|
|
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="modal-body" id="bulletinModalBody" style="font-size:1.1rem; line-height:1.5; word-break:break-word;"></div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|