{% extends "layout/backEndLayout.html.twig" %}
{% block stylesheets %}
{{ parent() }}
<style>
/* ══════════════════════════════════════════════════════
TOKENS
══════════════════════════════════════════════════════ */
:root {
--bg : #f0f4f8; --s1 : #ffffff; --s2 : #e8edf3;
--bdr : rgba(0,0,0,.09); --bdr-hi: rgba(0,0,0,.18);
--blue : #185fa5; --rose : #a32d2d; --amber: #854f0b;
--teal : #0f6e56; --em : #3b6d11; --vi : #534ab7;
--txt : #1a2535; --dim : #4a5568; --muted: #7a8899;
--r: 14px; --font: 'DM Sans','Segoe UI',sans-serif;
}
/* ── Layout ──────────────────────────────────────────── */
.cr-page { font-family:var(--font); color:var(--txt); }
.cr-page * { box-sizing:border-box; }
/* ── Entête classe ───────────────────────────────────── */
.cr-head {
background:var(--s1); border:1px solid var(--bdr);
border-radius:var(--r); padding:20px 24px; margin-bottom:20px;
display:flex; align-items:center; justify-content:space-between;
flex-wrap:wrap; gap:14px;
}
.cr-head__name { font-size:22px; font-weight:700; letter-spacing:-.02em; }
.cr-head__meta { font-size:13px; color:var(--dim); }
.cr-head__badges { display:flex; gap:8px; flex-wrap:wrap; }
.badge-pill {
display:inline-flex; align-items:center; gap:5px; padding:5px 12px;
border-radius:20px; font-size:12px; font-weight:500; border:1px solid var(--bdr);
}
.bp-blue { background:rgba(59,130,246,.15); color:var(--blue); }
.bp-amber { background:rgba(245,158,11,.15); color:var(--amber); }
.bp-teal { background:rgba(20,184,166,.15); color:var(--teal); }
/* ── Actions ─────────────────────────────────────────── */
.cr-actions {
background:var(--s1); border:1px solid var(--bdr); border-radius:var(--r);
padding:14px 18px; margin-bottom:20px; display:flex; flex-wrap:wrap; gap:8px;
}
.btn-cr {
display:inline-flex; align-items:center; gap:6px; padding:7px 14px;
border-radius:8px; font-size:12.5px; font-weight:500; text-decoration:none;
border:1px solid var(--bdr); cursor:pointer; transition:background .18s,transform .12s;
color:var(--txt); background:var(--s2);
}
.btn-cr:hover { transform:translateY(-1px); border-color:var(--bdr-hi); color:var(--txt); }
.btn-cr-blue { background:rgba(59,130,246,.15); color:var(--blue); border-color:rgba(59,130,246,.3); }
.btn-cr-amber { background:rgba(245,158,11,.15); color:var(--amber); border-color:rgba(245,158,11,.3); }
.btn-cr-teal { background:rgba(20,184,166,.15); color:var(--teal); border-color:rgba(20,184,166,.3); }
.btn-cr-rose { background:rgba(244,63,94,.15); color:var(--rose); border-color:rgba(244,63,94,.3); }
.btn-cr-vi { background:rgba(139,92,246,.15); color:var(--vi); border-color:rgba(139,92,246,.3); }
.btn-cr-em { background:rgba(16,185,129,.15); color:var(--em); border-color:rgba(16,185,129,.3); }
/* ── Tables ──────────────────────────────────────────── */
.cr-table-wrap {
background:var(--s1); border:1px solid var(--bdr); border-radius:var(--r);
overflow:hidden; margin-bottom:20px;
}
.cr-table-title {
padding:14px 18px 12px; border-bottom:1px solid var(--bdr);
font-size:14px; font-weight:600; display:flex; align-items:center; gap:8px;
}
.cr-table-title .dot { width:7px;height:7px;border-radius:50%; }
.d-bl{background:var(--blue);box-shadow:0 0 5px var(--blue);}
.d-am{background:var(--amber);box-shadow:0 0 5px var(--amber);}
table.cr-tbl { width:100%; border-collapse:collapse; font-size:13px; }
table.cr-tbl th { padding:10px 14px; color:var(--dim); font-weight:500; font-size:11.5px; text-transform:uppercase; letter-spacing:.05em; border-bottom:1px solid var(--bdr); background:var(--s2); }
table.cr-tbl td { padding:9px 14px; border-bottom:1px solid rgba(0,0,0,.05); }
table.cr-tbl tr:last-child td { border-bottom:none; }
table.cr-tbl tr:hover td { background:rgba(0,0,0,.025); }
.tag-exam { display:inline-block; background:rgba(244,63,94,.15); color:var(--rose); padding:2px 8px; border-radius:4px; font-size:11px; }
/* ── Grille graphiques ───────────────────────────────── */
.chart-grid { display:grid; gap:16px; margin-bottom:16px; }
.cg-2 { grid-template-columns:1fr 1fr; }
.cg-1 { grid-template-columns:1fr; }
@media(max-width:900px){ .cg-2 { grid-template-columns:1fr; } }
/* ── Carte graphique ─────────────────────────────────── */
.cc {
background:var(--s1); border:1px solid var(--bdr); border-radius:var(--r);
overflow:hidden; transition:border-color .2s; animation:cIn .4s ease both;
}
.cc:hover { border-color:var(--bdr-hi); }
.cc__hd {
display:flex; align-items:center; justify-content:space-between;
padding:13px 18px 11px; border-bottom:1px solid var(--bdr);
}
.cc__lbl { display:flex; align-items:center; gap:8px; font-size:13px; font-weight:600; }
.cc__dot { width:7px;height:7px;border-radius:50%;flex-shrink:0; }
.d-tl{background:var(--teal);box-shadow:0 0 5px var(--teal);}
.d-rs{background:var(--rose);box-shadow:0 0 5px var(--rose);}
.d-vi{background:var(--vi);box-shadow:0 0 5px var(--vi);}
.d-em{background:var(--em);box-shadow:0 0 5px var(--em);}
.cc__sub { font-size:11px; color:var(--muted); }
.cc__bd { padding:16px 18px 18px; }
/* ── Heatmap ─────────────────────────────────────────── */
.heatmap { overflow-x:auto; }
.heatmap table { width:100%; border-collapse:collapse; font-size:12px; }
.heatmap th { padding:7px 10px; color:var(--dim); font-size:11px; text-transform:uppercase; letter-spacing:.05em; text-align:center; font-weight:500; }
.heatmap th.course-th { text-align:left; min-width:140px; }
.heatmap td { padding:5px 8px; text-align:center; border:1px solid rgba(0,0,0,.05); font-weight:500; font-size:12.5px; border-radius:4px; transition:opacity .2s; }
.heatmap td:hover { opacity:.8; }
.heatmap td.course-td { text-align:left; color:var(--txt); font-weight:400; font-size:12px; padding-left:4px; background:transparent !important; border:none; }
.hm-null { background:rgba(0,0,0,.05); color:var(--muted); }
.hm-red { background:rgba(163,45,45,.15); color:#7f1d1d; }
.hm-yellow { background:rgba(133,79,11,.15); color:#451a03; }
.hm-green { background:rgba(15,110,86,.15); color:#064e3b; }
@keyframes cIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.cc:nth-child(1){animation-delay:.04s}.cc:nth-child(2){animation-delay:.1s}
#c-evol { max-height:220px; width:100%!important; }
#c-success{ max-height:260px; width:100%!important; }
#c-radar { max-height:300px; width:100%!important; }
#c-mentions{ max-height:220px; max-width:220px; margin:auto; display:block; }
</style>
{% endblock %}
{% block content %}
<div class="cr-page p-3">
{# ── Entête classe ────────────────────────────────────────────── #}
<div class="cr-head">
<div>
<div class="cr-head__name">{{ classroom.name }}</div>
<div class="cr-head__meta">
Niveau {{ classroom.level }}
{% if mainteacher %} · Titulaire : <strong>{{ mainteacher.fullName }}</strong>{% endif %}
</div>
</div>
<div class="cr-head__badges">
<span class="badge-pill bp-blue"><i class="fa fa-users"></i> {{ studentEnrolled|length }} élèves</span>
{% if classroom.apc %}
<span class="badge-pill bp-amber"><i class="fa fa-graduation-cap"></i> Classe d'examen</span>
{% endif %}
</div>
</div>
{# ── Boutons actions ──────────────────────────────────────────── #}
<div class="cr-actions">
<a class="btn-cr btn-cr-blue" href="{{ path('admin_classrooms') }}"><i class="fa fa-list"></i> Liste</a>
{% if is_granted('ROLE_ADMIN') %}
<a class="btn-cr btn-cr-blue" href="{{ path('admin_classrooms_edit', {id: classroom.id}) }}"><i class="fa fa-edit"></i> Modifier</a>
<a class="btn-cr btn-cr-amber" data-toggle="modal" data-target="#form_modal_courses" data-action="{{ path('admin_classrooms_recapitulatif_seq', {id: classroom.id}) }}"><i class="fa fa-file-pdf-o"></i> Recap Seq</a>
<a class="btn-cr btn-cr-amber" href="{{ path('admin_classrooms_reportcards_seq', {id: classroom.id}) }}"><i class="fa fa-file"></i> Bull Seq</a>
<a class="btn-cr btn-cr-teal" data-toggle="modal" data-target="#form_modal_reportcard_params" data-action="{{ path('admin_classrooms_reportcards_trim_2024', {id: classroom.id}) }}"><i class="fa fa-file"></i> Bull Trim</a>
<a class="btn-cr btn-cr-vi" data-toggle="modal" data-target="#form_modal_reportcard_params" data-action="{{ path('admin_class_reportcards_year_2024', {id: classroom.id}) }}"><i class="fa fa-file-pdf-o"></i> Bull Ann</a>
<a class="btn-cr btn-cr-rose" data-toggle="modal" data-target="#form_modal_courses" data-action="{{ path('admin_classrooms_recapitulatif_trim', {id: classroom.id}) }}"><i class="fa fa-file-pdf-o"></i> Recap Trim</a>
<a class="btn-cr btn-cr-em" data-toggle="modal" data-target="#form_modal_courses" data-action="{{ path('admin_classrooms_recapitulatif_ann_excel', {id: classroom.id}) }}"><i class="fa fa-file-excel-o"></i> Recap Ann</a>
{% endif %}
</div>
{# ── Tables élèves + matières ─────────────────────────────────── #}
<div class="cg-2 chart-grid">
<div class="cr-table-wrap">
<div class="cr-table-title"><span class="dot d-bl"></span>Élèves inscrits</div>
<table class="cr-tbl">
<thead><tr><th>Matricule</th><th>Nom</th><th>Prénom</th>{% if is_granted('ROLE_ADMIN') %}<th>Action</th>{% endif %}</tr></thead>
<tbody>
{% set effectif = 0 %}
{% for std in studentEnrolled %}
<tr>
<td><a href="{{ path('admin_students_show', {id: std.id}) }}" style="color:var(--blue)">{{ std.matricule }}</a></td>
<td>{{ std.lastname }}</td>
<td>{{ std.firstname }}</td>
{% if is_granted('ROLE_ADMIN') %}
<td>
<a class="btn-cr btn-cr-rose" style="padding:3px 8px;font-size:11px" href="{{ path('admin_students_unregister', {id: std.id, room_id: classroom.id}) }}"><i class="fa fa-ban"></i></a>
<a class="btn-cr" style="padding:3px 8px;font-size:11px" href="{{ path('admin_students_edit', {id: std.id}) }}"><i class="fa fa-pencil-square-o"></i></a>
</td>
{% endif %}
</tr>
{% set effectif = effectif + 1 %}
{% endfor %}
<tr><td colspan="2" style="color:var(--dim);font-size:11.5px">Effectif total</td><td style="font-weight:700;color:var(--blue)">{{ effectif }}</td>{% if is_granted('ROLE_ADMIN') %}<td></td>{% endif %}</tr>
</tbody>
</table>
</div>
<div class="cr-table-wrap">
<div class="cr-table-title"><span class="dot d-am"></span>Matières programmées</div>
<table class="cr-tbl">
<thead><tr><th>Module</th><th>Code</th><th>Intitulé</th><th>Coef</th><th>Enseignant</th></tr></thead>
<tbody>
{% set totalCoef = 0 %}
{% for module in modules %}
{% for course in module.courses %}
<tr>
<td style="color:var(--dim);font-size:11.5px">{{ module.name }}</td>
<td><a href="{{ path('admin_courses_show', {id: course.id}) }}" style="color:var(--blue)">{{ course.code }}</a></td>
<td>{{ course.wording }}</td>
<td style="font-weight:600">{{ course.coefficient }}</td>
<td>
{% if attributions[course.id] is defined %}
{% if is_granted('ROLE_ADMIN') %}
<a href="{{ path('admin_attributions_edit', {id: attributions[course.id].id}) }}" style="color:var(--teal)">{{ attributions[course.id].teacher.fullName }}</a>
{% else %}{{ attributions[course.id].teacher.fullName }}{% endif %}
{% else %}
{% if is_granted('ROLE_ADMIN') %}<a href="{{ path('admin_attributions_new') }}" style="color:var(--rose)">Non attribué</a>{% endif %}
{% endif %}
</td>
</tr>
{% set totalCoef = totalCoef + course.coefficient %}
{% endfor %}
{% endfor %}
<tr><td colspan="3" style="color:var(--dim);font-size:11.5px">Total coefficients</td><td style="font-weight:700;color:var(--amber)" colspan="2">{{ totalCoef }}</td></tr>
</tbody>
</table>
</div>
</div>
{# ══════════════════════════════════════════════════════════════
SECTION GRAPHIQUES
══════════════════════════════════════════════════════════════ #}
{# ── Graphique 1 : Heatmap cours × séquences ──────────────────
Résout le problème original : chaque cellule est indépendante,
null (pas d'éval) = gris neutre, aucun décalage possible.
─────────────────────────────────────────────────────────────── #}
<div class="cc" style="margin-bottom:16px">
<div class="cc__hd">
<div class="cc__lbl"><span class="cc__dot d-am"></span>Performances séquentielles — toutes matières</div>
<span class="cc__sub">rouge <10 · jaune 10–13 · vert ≥14 · gris = pas d'évaluation enregistrée</span>
</div>
<div class="cc__bd heatmap">
<table id="heatmap-table">
<thead>
<tr>
<th class="course-th">Matière</th>
{# Les en-têtes de séquences sont injectées par JS #}
</tr>
</thead>
<tbody id="heatmap-body"></tbody>
</table>
</div>
</div>
{# ── Graphique 2 + 3 : Évolution + Réussite/Échec ─────────── #}
<div class="chart-grid cg-2">
<div class="cc">
<div class="cc__hd">
<div class="cc__lbl"><span class="cc__dot d-tl"></span>Évolution de la moyenne générale</div>
<span class="cc__sub">séquence par séquence · ligne rouge = seuil 10</span>
</div>
<div class="cc__bd"><canvas id="c-evol"></canvas></div>
</div>
<div class="cc">
<div class="cc__hd">
<div class="cc__lbl"><span class="cc__dot d-rs"></span>Réussite vs Échec — {{ activeSeqLabel }}</div>
<span class="cc__sub">séquence active · avec absents</span>
</div>
<div class="cc__bd"><canvas id="c-success"></canvas></div>
</div>
</div>
{# ── Graphique 4 + 5 : Radar + Donut ─────────────────────────
Radar : profil trimestriel de la classe (points forts/faibles)
Donut : examens officiels (seulement si classe d'examen)
─────────────────────────────────────────────────────────────── #}
<div class="chart-grid {{ classroom.apc ? 'cg-2' : 'cg-1' }}">
<div class="cc">
<div class="cc__hd">
<div class="cc__lbl"><span class="cc__dot d-vi"></span>Profil trimestriel de la classe</div>
<span class="cc__sub">radar par matière · T1, T2, T3</span>
</div>
<div class="cc__bd"><canvas id="c-radar"></canvas></div>
</div>
{% if classroom.apc %}
<div class="cc">
<div class="cc__hd">
<div class="cc__lbl"><span class="cc__dot d-em"></span>Résultats examens officiels</div>
<span class="cc__sub">répartition des mentions</span>
</div>
<div class="cc__bd" style="display:flex;align-items:center;justify-content:center">
<canvas id="c-mentions"></canvas>
</div>
</div>
{% endif %}
</div>
</div>{# /.cr-page #}
{# ══════════════════════════════════════════════════════════════
MODALES (inchangées)
══════════════════════════════════════════════════════════════ #}
<div class="modal fade" id="form_modal_courses" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="background:#ffffff;color:#1a2535;border:1px solid rgba(0,0,0,.12)">
<div class="modal-header border-bottom-0">
<h5 class="modal-title">Choisir les cours du récapitulatif</h5>
<button type="button" class="close" data-dismiss="modal" style="color:var(--txt)"><span>×</span></button>
</div>
<form action="#" method="post">
<div class="modal-body">
{% for module in modules %}{% for course in module.courses %}
<li class="list-group-item" style="background:transparent;border-color:var(--bdr);color:var(--txt)">
<input class="form-check-input me-1" type="checkbox" checked name="selected_courses[]" value="{{ course.id }}">
{{ course.wording }}
</li>
{% endfor %}{% endfor %}
</div>
<div class="modal-footer border-top-0 d-flex justify-content-center">
<button type="submit" class="btn-cr btn-cr-em">Générer</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="form_modal_reportcard_params" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="background:#ffffff;color:#1a2535;border:1px solid rgba(0,0,0,.12)">
<div class="modal-header border-bottom-0">
<h5 class="modal-title">Paramètres du bulletin</h5>
<button type="button" class="close" data-dismiss="modal" style="color:var(--txt)"><span>×</span></button>
</div>
<form method="post">
<div class="modal-body">
<div class="form-group mb-3">
<label>Taille des caractères (entête)</label>
<input type="range" class="form-control" name="header_font_size" min="1" max="1.6" step="0.01">
</div>
<div class="form-group mb-3">
<label>Hauteur des lignes</label>
<input type="range" class="form-control" name="line_height" min="0.5" max="1.5" step="0.01">
</div>
<div class="form-check"><input class="form-check-input" type="checkbox" name="copyright" id="copyright"><label class="form-check-label" for="copyright">Copyright en pied de page</label></div>
<div class="form-check"><input class="form-check-input" type="checkbox" name="reverse" id="reverse"><label class="form-check-label" for="reverse">Sens inverse</label></div>
</div>
<div class="modal-footer border-top-0 d-flex justify-content-center">
<button type="submit" class="btn-cr btn-cr-blue">Générer</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
(function(){
'use strict';
/* ── Données depuis le controller ─────────────────────── */
const courseLabels = {{ courseLabels | raw }};
const seqLabels = {{ seqLabels | raw }};
const seqIds = {{ seqIds | raw }};
const heatmapData = {{ heatmapData | raw }};
const generalAvgBySeq = {{ generalAvgBySeq | raw }};
const successFailData = {{ successFailData | raw }};
const quaterProfiles = {{ quaterProfiles | raw }};
{% if classroom.apc %}
const mentionCategories = {{ mentionCategories | raw }};
const mentionCountCategories = {{ mentionCountCategories | raw }};
{% endif %}
/* ── Palette & defaults ───────────────────────────────── */
const C = {
blue:'#185fa5', rose:'#a32d2d', amber:'#854f0b',
teal:'#0f6e56', vi:'#534ab7', em:'#3b6d11', orange:'#9a4f0b',
grid:'rgba(0,0,0,.07)', bg:'#ffffff', txt:'#1a2535', dim:'#4a5568'
};
const PALQ = ['rgba(24,95,165,.35)','rgba(15,110,86,.35)','rgba(83,74,183,.35)'];
const PALQ_BD = ['#185fa5', '#0f6e56', '#534ab7'];
const PAL = ['#3b82f6','#f59e0b','#f43f5e','#10b981','#8b5cf6','#14b8a6','#f97316','#84cc16'];
Chart.defaults.color = C.dim;
Chart.defaults.font.family = "'DM Sans','Segoe UI',sans-serif";
Chart.defaults.font.size = 12;
const TIP = {backgroundColor:'#ffffff',borderColor:'rgba(0,0,0,.12)',borderWidth:1,titleColor:C.txt,bodyColor:C.dim,padding:12,cornerRadius:10};
const AX = (o={}) => ({grid:{color:C.grid,drawBorder:false},ticks:{color:C.dim},border:{display:false},...o});
/* ══════════════════════════════════════════════════════
1. HEATMAP — table HTML colorée cours × séquences
Résout le problème de départ : chaque cellule est
indépendante. null = gris "pas d'éval enregistrée".
══════════════════════════════════════════════════════ */
(function buildHeatmap(){
const thead = document.querySelector('#heatmap-table thead tr');
const tbody = document.getElementById('heatmap-body');
// En-têtes séquences
seqLabels.forEach(lbl => {
const th = document.createElement('th');
th.textContent = lbl;
thead.appendChild(th);
});
// Colonne moyenne
const thAvg = document.createElement('th');
thAvg.textContent = 'Moy.';
thead.appendChild(thAvg);
// Lignes de données
heatmapData.forEach(row => {
const tr = document.createElement('tr');
// Cellule nom de la matière
const tdName = document.createElement('td');
tdName.className = 'course-td';
tdName.textContent = row.course;
tr.appendChild(tdName);
let sum = 0, cnt = 0;
// Cellules par séquence
seqIds.forEach(sid => {
const val = row['s' + sid];
const td = document.createElement('td');
if (val === null || val === undefined) {
td.className = 'hm-null';
td.textContent = '—';
} else {
td.className = val < 10 ? 'hm-red' : val < 14 ? 'hm-yellow' : 'hm-green';
td.textContent = val.toFixed(1);
sum += val; cnt++;
}
tr.appendChild(td);
});
// Cellule moyenne
const tdAvg = document.createElement('td');
if (cnt > 0) {
const avg = sum / cnt;
tdAvg.className = avg < 10 ? 'hm-red' : avg < 14 ? 'hm-yellow' : 'hm-green';
tdAvg.textContent = avg.toFixed(1);
tdAvg.style.fontWeight = '700';
} else {
tdAvg.className = 'hm-null';
tdAvg.textContent = '—';
}
tr.appendChild(tdAvg);
tbody.appendChild(tr);
});
})();
/* ══════════════════════════════════════════════════════
2. ÉVOLUTION MOYENNE GÉNÉRALE
null = séquence pas encore notée → pas de point tracé
══════════════════════════════════════════════════════ */
if (document.getElementById('c-evol')) {
new Chart('c-evol', {
type: 'line',
data: {
labels: seqLabels,
datasets: [{
label: 'Moyenne générale',
data : generalAvgBySeq,
borderColor: '#0f6e56', backgroundColor: 'rgba(15,110,86,.08)',
borderWidth: 2.5, pointBackgroundColor: '#0f6e56',
pointRadius: generalAvgBySeq.map(v => v !== null ? 5 : 0),
pointHoverRadius: 7, tension: .3, fill: true,
spanGaps: false, // ne pas relier les points si null entre eux
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
tooltip: { ...TIP, callbacks: { label: c => c.raw !== null ? ' Moy. : ' + c.raw + '/20' : ' Pas d\'évaluation' } },
},
scales: {
x: { ...AX() },
y: { ...AX(), min: 0, max: 20, ticks: { ...AX().ticks, stepSize: 5 } },
},
},
plugins: [{
id: 'seuil',
afterDraw(ch) {
const { ctx, chartArea: { left, right }, scales: { y } } = ch;
const yp = y.getPixelForValue(10);
ctx.save();
ctx.beginPath(); ctx.moveTo(left, yp); ctx.lineTo(right, yp);
ctx.strokeStyle = 'rgba(163,45,45,.5)'; ctx.setLineDash([5, 4]);
ctx.lineWidth = 1.5; ctx.stroke();
ctx.fillStyle = 'rgba(163,45,45,.7)'; ctx.font = '11px DM Sans';
ctx.fillText('Seuil 10', left + 4, yp - 5);
ctx.restore();
}
}]
});
}
/* ══════════════════════════════════════════════════════
3. RÉUSSITE VS ÉCHEC (barres empilées)
Données depuis evaluation.success_h/f + faillures_h/f
→ pas depuis mark, déjà agrégées par le controller
══════════════════════════════════════════════════════ */
if (document.getElementById('c-success') && successFailData.labels.length > 0) {
new Chart('c-success', {
type: 'bar',
data: {
labels: successFailData.labels,
datasets: [
{
label: 'Réussite', data: successFailData.success,
backgroundColor: 'rgba(59,109,17,.55)', borderColor: C.em,
borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
},
{
label: 'Échec', data: successFailData.failures,
backgroundColor: 'rgba(163,45,45,.55)', borderColor: C.rose,
borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
},
{
label: 'Absents', data: successFailData.absents,
backgroundColor: 'rgba(133,79,11,.45)', borderColor: C.amber,
borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
},
],
},
options: {
responsive: true,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { position: 'top', align: 'end', labels: { boxWidth: 9, boxHeight: 9, borderRadius: 3, useBorderRadius: true, color: C.txt, padding: 12 } },
tooltip: { ...TIP },
},
scales: {
x: { ...AX(), stacked: true, ticks: { ...AX().ticks, maxRotation: 35 } },
y: { ...AX(), stacked: true, beginAtZero: true },
},
},
});
}
/* ══════════════════════════════════════════════════════
4. RADAR PAR TRIMESTRE
Révèle le profil de la classe : quelle matière est
forte/faible, stable ou en progression entre trimestres
══════════════════════════════════════════════════════ */
if (document.getElementById('c-radar') && quaterProfiles.length > 0) {
new Chart('c-radar', {
type: 'radar',
data: {
labels: courseLabels,
datasets: quaterProfiles.map((qp, i) => ({
label : qp.label,
data : qp.data,
backgroundColor : PALQ[i % PALQ.length],
borderColor : PALQ_BD[i % PALQ_BD.length],
borderWidth : 2,
pointBackgroundColor: PALQ_BD[i % PALQ_BD.length],
pointRadius : 4,
spanGaps : true,
})),
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top', align: 'end',
labels: { boxWidth: 9, boxHeight: 9, borderRadius: 3, useBorderRadius: true, color: C.txt, padding: 12 }
},
tooltip: { ...TIP, callbacks: { label: c => ' ' + c.dataset.label + ' : ' + (c.raw !== null ? c.raw + '/20' : '—') } },
},
scales: {
r: {
min: 0, max: 20,
backgroundColor: 'rgba(24,95,165,.04)',
grid : { color: C.grid },
angleLines : { color: C.grid },
ticks : { color: C.dim, backdropColor: 'transparent', stepSize: 5 },
pointLabels : { color: C.dim, font: { size: 11 } },
},
},
},
});
}
/* ══════════════════════════════════════════════════════
5. DONUT EXAMENS OFFICIELS (seulement si classe examen)
══════════════════════════════════════════════════════ */
{% if classroom.apc %}
if (document.getElementById('c-mentions') && mentionCountCategories.length > 0) {
new Chart('c-mentions', {
type: 'doughnut',
data: {
labels: mentionCategories,
datasets: [{
data: mentionCountCategories,
backgroundColor: PAL.slice(0, mentionCategories.length),
borderColor: '#f0f4f8', borderWidth: 3, hoverOffset: 8,
}],
},
options: {
responsive: true, cutout: '58%',
plugins: {
legend: { position: 'bottom', labels: { boxWidth: 8, boxHeight: 8, borderRadius: 2, useBorderRadius: true, color: C.txt, padding: 8, font: { size: 11 } } },
tooltip: { ...TIP, callbacks: { label: c => ' ' + c.label + ' : ' + c.raw } },
},
},
});
}
{% endif %}
/* ── Modales actions ──────────────────────────────────── */
$(document).ready(function () {
$('#form_modal_courses').on('show.bs.modal', function (e) {
$('#form_modal_courses form').attr('action', $(e.relatedTarget).data('action'));
});
$('[data-toggle="modal"]').on('click', function () {
$('#form_modal_reportcard_params form').attr('action', $(this).data('action'));
});
});
})();
</script>
{% endblock %}