templates/classroom/show.html.twig line 1

Open in your IDE?
  1. {% extends "layout/backEndLayout.html.twig" %}
  2. {% block stylesheets %}
  3. {{ parent() }}
  4. <style>
  5. /* ══════════════════════════════════════════════════════
  6. TOKENS
  7. ══════════════════════════════════════════════════════ */
  8. :root {
  9. --bg : #f0f4f8; --s1 : #ffffff; --s2 : #e8edf3;
  10. --bdr : rgba(0,0,0,.09); --bdr-hi: rgba(0,0,0,.18);
  11. --blue : #185fa5; --rose : #a32d2d; --amber: #854f0b;
  12. --teal : #0f6e56; --em : #3b6d11; --vi : #534ab7;
  13. --txt : #1a2535; --dim : #4a5568; --muted: #7a8899;
  14. --r: 14px; --font: 'DM Sans','Segoe UI',sans-serif;
  15. }
  16. /* ── Layout ──────────────────────────────────────────── */
  17. .cr-page { font-family:var(--font); color:var(--txt); }
  18. .cr-page * { box-sizing:border-box; }
  19. /* ── Entête classe ───────────────────────────────────── */
  20. .cr-head {
  21. background:var(--s1); border:1px solid var(--bdr);
  22. border-radius:var(--r); padding:20px 24px; margin-bottom:20px;
  23. display:flex; align-items:center; justify-content:space-between;
  24. flex-wrap:wrap; gap:14px;
  25. }
  26. .cr-head__name { font-size:22px; font-weight:700; letter-spacing:-.02em; }
  27. .cr-head__meta { font-size:13px; color:var(--dim); }
  28. .cr-head__badges { display:flex; gap:8px; flex-wrap:wrap; }
  29. .badge-pill {
  30. display:inline-flex; align-items:center; gap:5px; padding:5px 12px;
  31. border-radius:20px; font-size:12px; font-weight:500; border:1px solid var(--bdr);
  32. }
  33. .bp-blue { background:rgba(59,130,246,.15); color:var(--blue); }
  34. .bp-amber { background:rgba(245,158,11,.15); color:var(--amber); }
  35. .bp-teal { background:rgba(20,184,166,.15); color:var(--teal); }
  36. /* ── Actions ─────────────────────────────────────────── */
  37. .cr-actions {
  38. background:var(--s1); border:1px solid var(--bdr); border-radius:var(--r);
  39. padding:14px 18px; margin-bottom:20px; display:flex; flex-wrap:wrap; gap:8px;
  40. }
  41. .btn-cr {
  42. display:inline-flex; align-items:center; gap:6px; padding:7px 14px;
  43. border-radius:8px; font-size:12.5px; font-weight:500; text-decoration:none;
  44. border:1px solid var(--bdr); cursor:pointer; transition:background .18s,transform .12s;
  45. color:var(--txt); background:var(--s2);
  46. }
  47. .btn-cr:hover { transform:translateY(-1px); border-color:var(--bdr-hi); color:var(--txt); }
  48. .btn-cr-blue { background:rgba(59,130,246,.15); color:var(--blue); border-color:rgba(59,130,246,.3); }
  49. .btn-cr-amber { background:rgba(245,158,11,.15); color:var(--amber); border-color:rgba(245,158,11,.3); }
  50. .btn-cr-teal { background:rgba(20,184,166,.15); color:var(--teal); border-color:rgba(20,184,166,.3); }
  51. .btn-cr-rose { background:rgba(244,63,94,.15); color:var(--rose); border-color:rgba(244,63,94,.3); }
  52. .btn-cr-vi { background:rgba(139,92,246,.15); color:var(--vi); border-color:rgba(139,92,246,.3); }
  53. .btn-cr-em { background:rgba(16,185,129,.15); color:var(--em); border-color:rgba(16,185,129,.3); }
  54. /* ── Tables ──────────────────────────────────────────── */
  55. .cr-table-wrap {
  56. background:var(--s1); border:1px solid var(--bdr); border-radius:var(--r);
  57. overflow:hidden; margin-bottom:20px;
  58. }
  59. .cr-table-title {
  60. padding:14px 18px 12px; border-bottom:1px solid var(--bdr);
  61. font-size:14px; font-weight:600; display:flex; align-items:center; gap:8px;
  62. }
  63. .cr-table-title .dot { width:7px;height:7px;border-radius:50%; }
  64. .d-bl{background:var(--blue);box-shadow:0 0 5px var(--blue);}
  65. .d-am{background:var(--amber);box-shadow:0 0 5px var(--amber);}
  66. table.cr-tbl { width:100%; border-collapse:collapse; font-size:13px; }
  67. 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); }
  68. table.cr-tbl td { padding:9px 14px; border-bottom:1px solid rgba(0,0,0,.05); }
  69. table.cr-tbl tr:last-child td { border-bottom:none; }
  70. table.cr-tbl tr:hover td { background:rgba(0,0,0,.025); }
  71. .tag-exam { display:inline-block; background:rgba(244,63,94,.15); color:var(--rose); padding:2px 8px; border-radius:4px; font-size:11px; }
  72. /* ── Grille graphiques ───────────────────────────────── */
  73. .chart-grid { display:grid; gap:16px; margin-bottom:16px; }
  74. .cg-2 { grid-template-columns:1fr 1fr; }
  75. .cg-1 { grid-template-columns:1fr; }
  76. @media(max-width:900px){ .cg-2 { grid-template-columns:1fr; } }
  77. /* ── Carte graphique ─────────────────────────────────── */
  78. .cc {
  79. background:var(--s1); border:1px solid var(--bdr); border-radius:var(--r);
  80. overflow:hidden; transition:border-color .2s; animation:cIn .4s ease both;
  81. }
  82. .cc:hover { border-color:var(--bdr-hi); }
  83. .cc__hd {
  84. display:flex; align-items:center; justify-content:space-between;
  85. padding:13px 18px 11px; border-bottom:1px solid var(--bdr);
  86. }
  87. .cc__lbl { display:flex; align-items:center; gap:8px; font-size:13px; font-weight:600; }
  88. .cc__dot { width:7px;height:7px;border-radius:50%;flex-shrink:0; }
  89. .d-tl{background:var(--teal);box-shadow:0 0 5px var(--teal);}
  90. .d-rs{background:var(--rose);box-shadow:0 0 5px var(--rose);}
  91. .d-vi{background:var(--vi);box-shadow:0 0 5px var(--vi);}
  92. .d-em{background:var(--em);box-shadow:0 0 5px var(--em);}
  93. .cc__sub { font-size:11px; color:var(--muted); }
  94. .cc__bd { padding:16px 18px 18px; }
  95. /* ── Heatmap ─────────────────────────────────────────── */
  96. .heatmap { overflow-x:auto; }
  97. .heatmap table { width:100%; border-collapse:collapse; font-size:12px; }
  98. .heatmap th { padding:7px 10px; color:var(--dim); font-size:11px; text-transform:uppercase; letter-spacing:.05em; text-align:center; font-weight:500; }
  99. .heatmap th.course-th { text-align:left; min-width:140px; }
  100. .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; }
  101. .heatmap td:hover { opacity:.8; }
  102. .heatmap td.course-td { text-align:left; color:var(--txt); font-weight:400; font-size:12px; padding-left:4px; background:transparent !important; border:none; }
  103. .hm-null { background:rgba(0,0,0,.05); color:var(--muted); }
  104. .hm-red { background:rgba(163,45,45,.15); color:#7f1d1d; }
  105. .hm-yellow { background:rgba(133,79,11,.15); color:#451a03; }
  106. .hm-green { background:rgba(15,110,86,.15); color:#064e3b; }
  107. @keyframes cIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
  108. .cc:nth-child(1){animation-delay:.04s}.cc:nth-child(2){animation-delay:.1s}
  109. #c-evol { max-height:220px; width:100%!important; }
  110. #c-success{ max-height:260px; width:100%!important; }
  111. #c-radar { max-height:300px; width:100%!important; }
  112. #c-mentions{ max-height:220px; max-width:220px; margin:auto; display:block; }
  113. </style>
  114. {% endblock %}
  115. {% block content %}
  116. <div class="cr-page p-3">
  117. {# ── Entête classe ────────────────────────────────────────────── #}
  118. <div class="cr-head">
  119. <div>
  120. <div class="cr-head__name">{{ classroom.name }}</div>
  121. <div class="cr-head__meta">
  122. Niveau {{ classroom.level }}
  123. {% if mainteacher %} · Titulaire : <strong>{{ mainteacher.fullName }}</strong>{% endif %}
  124. </div>
  125. </div>
  126. <div class="cr-head__badges">
  127. <span class="badge-pill bp-blue"><i class="fa fa-users"></i> {{ studentEnrolled|length }} élèves</span>
  128. {% if classroom.apc %}
  129. <span class="badge-pill bp-amber"><i class="fa fa-graduation-cap"></i> Classe d'examen</span>
  130. {% endif %}
  131. </div>
  132. </div>
  133. {# ── Boutons actions ──────────────────────────────────────────── #}
  134. <div class="cr-actions">
  135. <a class="btn-cr btn-cr-blue" href="{{ path('admin_classrooms') }}"><i class="fa fa-list"></i> Liste</a>
  136. {% if is_granted('ROLE_ADMIN') %}
  137. <a class="btn-cr btn-cr-blue" href="{{ path('admin_classrooms_edit', {id: classroom.id}) }}"><i class="fa fa-edit"></i> Modifier</a>
  138. <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>
  139. <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>
  140. <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>
  141. <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>
  142. <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>
  143. <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>
  144. {% endif %}
  145. </div>
  146. {# ── Tables élèves + matières ─────────────────────────────────── #}
  147. <div class="cg-2 chart-grid">
  148. <div class="cr-table-wrap">
  149. <div class="cr-table-title"><span class="dot d-bl"></span>Élèves inscrits</div>
  150. <table class="cr-tbl">
  151. <thead><tr><th>Matricule</th><th>Nom</th><th>Prénom</th>{% if is_granted('ROLE_ADMIN') %}<th>Action</th>{% endif %}</tr></thead>
  152. <tbody>
  153. {% set effectif = 0 %}
  154. {% for std in studentEnrolled %}
  155. <tr>
  156. <td><a href="{{ path('admin_students_show', {id: std.id}) }}" style="color:var(--blue)">{{ std.matricule }}</a></td>
  157. <td>{{ std.lastname }}</td>
  158. <td>{{ std.firstname }}</td>
  159. {% if is_granted('ROLE_ADMIN') %}
  160. <td>
  161. <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>
  162. <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>
  163. </td>
  164. {% endif %}
  165. </tr>
  166. {% set effectif = effectif + 1 %}
  167. {% endfor %}
  168. <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>
  169. </tbody>
  170. </table>
  171. </div>
  172. <div class="cr-table-wrap">
  173. <div class="cr-table-title"><span class="dot d-am"></span>Matières programmées</div>
  174. <table class="cr-tbl">
  175. <thead><tr><th>Module</th><th>Code</th><th>Intitulé</th><th>Coef</th><th>Enseignant</th></tr></thead>
  176. <tbody>
  177. {% set totalCoef = 0 %}
  178. {% for module in modules %}
  179. {% for course in module.courses %}
  180. <tr>
  181. <td style="color:var(--dim);font-size:11.5px">{{ module.name }}</td>
  182. <td><a href="{{ path('admin_courses_show', {id: course.id}) }}" style="color:var(--blue)">{{ course.code }}</a></td>
  183. <td>{{ course.wording }}</td>
  184. <td style="font-weight:600">{{ course.coefficient }}</td>
  185. <td>
  186. {% if attributions[course.id] is defined %}
  187. {% if is_granted('ROLE_ADMIN') %}
  188. <a href="{{ path('admin_attributions_edit', {id: attributions[course.id].id}) }}" style="color:var(--teal)">{{ attributions[course.id].teacher.fullName }}</a>
  189. {% else %}{{ attributions[course.id].teacher.fullName }}{% endif %}
  190. {% else %}
  191. {% if is_granted('ROLE_ADMIN') %}<a href="{{ path('admin_attributions_new') }}" style="color:var(--rose)">Non attribué</a>{% endif %}
  192. {% endif %}
  193. </td>
  194. </tr>
  195. {% set totalCoef = totalCoef + course.coefficient %}
  196. {% endfor %}
  197. {% endfor %}
  198. <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>
  199. </tbody>
  200. </table>
  201. </div>
  202. </div>
  203. {# ══════════════════════════════════════════════════════════════
  204. SECTION GRAPHIQUES
  205. ══════════════════════════════════════════════════════════════ #}
  206. {# ── Graphique 1 : Heatmap cours × séquences ──────────────────
  207. Résout le problème original : chaque cellule est indépendante,
  208. null (pas d'éval) = gris neutre, aucun décalage possible.
  209. ─────────────────────────────────────────────────────────────── #}
  210. <div class="cc" style="margin-bottom:16px">
  211. <div class="cc__hd">
  212. <div class="cc__lbl"><span class="cc__dot d-am"></span>Performances séquentielles — toutes matières</div>
  213. <span class="cc__sub">rouge &lt;10 · jaune 10–13 · vert ≥14 · gris = pas d'évaluation enregistrée</span>
  214. </div>
  215. <div class="cc__bd heatmap">
  216. <table id="heatmap-table">
  217. <thead>
  218. <tr>
  219. <th class="course-th">Matière</th>
  220. {# Les en-têtes de séquences sont injectées par JS #}
  221. </tr>
  222. </thead>
  223. <tbody id="heatmap-body"></tbody>
  224. </table>
  225. </div>
  226. </div>
  227. {# ── Graphique 2 + 3 : Évolution + Réussite/Échec ─────────── #}
  228. <div class="chart-grid cg-2">
  229. <div class="cc">
  230. <div class="cc__hd">
  231. <div class="cc__lbl"><span class="cc__dot d-tl"></span>Évolution de la moyenne générale</div>
  232. <span class="cc__sub">séquence par séquence · ligne rouge = seuil 10</span>
  233. </div>
  234. <div class="cc__bd"><canvas id="c-evol"></canvas></div>
  235. </div>
  236. <div class="cc">
  237. <div class="cc__hd">
  238. <div class="cc__lbl"><span class="cc__dot d-rs"></span>Réussite vs Échec — {{ activeSeqLabel }}</div>
  239. <span class="cc__sub">séquence active · avec absents</span>
  240. </div>
  241. <div class="cc__bd"><canvas id="c-success"></canvas></div>
  242. </div>
  243. </div>
  244. {# ── Graphique 4 + 5 : Radar + Donut ─────────────────────────
  245. Radar : profil trimestriel de la classe (points forts/faibles)
  246. Donut : examens officiels (seulement si classe d'examen)
  247. ─────────────────────────────────────────────────────────────── #}
  248. <div class="chart-grid {{ classroom.apc ? 'cg-2' : 'cg-1' }}">
  249. <div class="cc">
  250. <div class="cc__hd">
  251. <div class="cc__lbl"><span class="cc__dot d-vi"></span>Profil trimestriel de la classe</div>
  252. <span class="cc__sub">radar par matière · T1, T2, T3</span>
  253. </div>
  254. <div class="cc__bd"><canvas id="c-radar"></canvas></div>
  255. </div>
  256. {% if classroom.apc %}
  257. <div class="cc">
  258. <div class="cc__hd">
  259. <div class="cc__lbl"><span class="cc__dot d-em"></span>Résultats examens officiels</div>
  260. <span class="cc__sub">répartition des mentions</span>
  261. </div>
  262. <div class="cc__bd" style="display:flex;align-items:center;justify-content:center">
  263. <canvas id="c-mentions"></canvas>
  264. </div>
  265. </div>
  266. {% endif %}
  267. </div>
  268. </div>{# /.cr-page #}
  269. {# ══════════════════════════════════════════════════════════════
  270. MODALES (inchangées)
  271. ══════════════════════════════════════════════════════════════ #}
  272. <div class="modal fade" id="form_modal_courses" tabindex="-1" role="dialog">
  273. <div class="modal-dialog modal-dialog-centered">
  274. <div class="modal-content" style="background:#ffffff;color:#1a2535;border:1px solid rgba(0,0,0,.12)">
  275. <div class="modal-header border-bottom-0">
  276. <h5 class="modal-title">Choisir les cours du récapitulatif</h5>
  277. <button type="button" class="close" data-dismiss="modal" style="color:var(--txt)"><span>&times;</span></button>
  278. </div>
  279. <form action="#" method="post">
  280. <div class="modal-body">
  281. {% for module in modules %}{% for course in module.courses %}
  282. <li class="list-group-item" style="background:transparent;border-color:var(--bdr);color:var(--txt)">
  283. <input class="form-check-input me-1" type="checkbox" checked name="selected_courses[]" value="{{ course.id }}">
  284. {{ course.wording }}
  285. </li>
  286. {% endfor %}{% endfor %}
  287. </div>
  288. <div class="modal-footer border-top-0 d-flex justify-content-center">
  289. <button type="submit" class="btn-cr btn-cr-em">Générer</button>
  290. </div>
  291. </form>
  292. </div>
  293. </div>
  294. </div>
  295. <div class="modal fade" id="form_modal_reportcard_params" tabindex="-1" role="dialog">
  296. <div class="modal-dialog modal-dialog-centered">
  297. <div class="modal-content" style="background:#ffffff;color:#1a2535;border:1px solid rgba(0,0,0,.12)">
  298. <div class="modal-header border-bottom-0">
  299. <h5 class="modal-title">Paramètres du bulletin</h5>
  300. <button type="button" class="close" data-dismiss="modal" style="color:var(--txt)"><span>&times;</span></button>
  301. </div>
  302. <form method="post">
  303. <div class="modal-body">
  304. <div class="form-check mb-2"><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>
  305. <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>
  306. </div>
  307. <div class="modal-footer border-top-0 d-flex justify-content-center">
  308. <button type="submit" class="btn-cr btn-cr-blue">Générer</button>
  309. </div>
  310. </form>
  311. </div>
  312. </div>
  313. </div>
  314. {% endblock %}
  315. {% block javascripts %}
  316. {{ parent() }}
  317. <script>
  318. (function(){
  319. 'use strict';
  320. /* ── Données depuis le controller ─────────────────────── */
  321. const courseLabels = {{ courseLabels | raw }};
  322. const seqLabels = {{ seqLabels | raw }};
  323. const seqIds = {{ seqIds | raw }};
  324. const heatmapData = {{ heatmapData | raw }};
  325. const generalAvgBySeq = {{ generalAvgBySeq | raw }};
  326. const successFailData = {{ successFailData | raw }};
  327. const quaterProfiles = {{ quaterProfiles | raw }};
  328. {% if classroom.apc %}
  329. const mentionCategories = {{ mentionCategories | raw }};
  330. const mentionCountCategories = {{ mentionCountCategories | raw }};
  331. {% endif %}
  332. /* ── Palette & defaults ───────────────────────────────── */
  333. const C = {
  334. blue:'#185fa5', rose:'#a32d2d', amber:'#854f0b',
  335. teal:'#0f6e56', vi:'#534ab7', em:'#3b6d11', orange:'#9a4f0b',
  336. grid:'rgba(0,0,0,.07)', bg:'#ffffff', txt:'#1a2535', dim:'#4a5568'
  337. };
  338. const PALQ = ['rgba(24,95,165,.35)','rgba(15,110,86,.35)','rgba(83,74,183,.35)'];
  339. const PALQ_BD = ['#185fa5', '#0f6e56', '#534ab7'];
  340. const PAL = ['#3b82f6','#f59e0b','#f43f5e','#10b981','#8b5cf6','#14b8a6','#f97316','#84cc16'];
  341. Chart.defaults.color = C.dim;
  342. Chart.defaults.font.family = "'DM Sans','Segoe UI',sans-serif";
  343. Chart.defaults.font.size = 12;
  344. const TIP = {backgroundColor:'#ffffff',borderColor:'rgba(0,0,0,.12)',borderWidth:1,titleColor:C.txt,bodyColor:C.dim,padding:12,cornerRadius:10};
  345. const AX = (o={}) => ({grid:{color:C.grid,drawBorder:false},ticks:{color:C.dim},border:{display:false},...o});
  346. /* ══════════════════════════════════════════════════════
  347. 1. HEATMAP — table HTML colorée cours × séquences
  348. Résout le problème de départ : chaque cellule est
  349. indépendante. null = gris "pas d'éval enregistrée".
  350. ══════════════════════════════════════════════════════ */
  351. (function buildHeatmap(){
  352. const thead = document.querySelector('#heatmap-table thead tr');
  353. const tbody = document.getElementById('heatmap-body');
  354. // En-têtes séquences
  355. seqLabels.forEach(lbl => {
  356. const th = document.createElement('th');
  357. th.textContent = lbl;
  358. thead.appendChild(th);
  359. });
  360. // Colonne moyenne
  361. const thAvg = document.createElement('th');
  362. thAvg.textContent = 'Moy.';
  363. thead.appendChild(thAvg);
  364. // Lignes de données
  365. heatmapData.forEach(row => {
  366. const tr = document.createElement('tr');
  367. // Cellule nom de la matière
  368. const tdName = document.createElement('td');
  369. tdName.className = 'course-td';
  370. tdName.textContent = row.course;
  371. tr.appendChild(tdName);
  372. let sum = 0, cnt = 0;
  373. // Cellules par séquence
  374. seqIds.forEach(sid => {
  375. const val = row['s' + sid];
  376. const td = document.createElement('td');
  377. if (val === null || val === undefined) {
  378. td.className = 'hm-null';
  379. td.textContent = '—';
  380. } else {
  381. td.className = val < 10 ? 'hm-red' : val < 14 ? 'hm-yellow' : 'hm-green';
  382. td.textContent = val.toFixed(1);
  383. sum += val; cnt++;
  384. }
  385. tr.appendChild(td);
  386. });
  387. // Cellule moyenne
  388. const tdAvg = document.createElement('td');
  389. if (cnt > 0) {
  390. const avg = sum / cnt;
  391. tdAvg.className = avg < 10 ? 'hm-red' : avg < 14 ? 'hm-yellow' : 'hm-green';
  392. tdAvg.textContent = avg.toFixed(1);
  393. tdAvg.style.fontWeight = '700';
  394. } else {
  395. tdAvg.className = 'hm-null';
  396. tdAvg.textContent = '—';
  397. }
  398. tr.appendChild(tdAvg);
  399. tbody.appendChild(tr);
  400. });
  401. })();
  402. /* ══════════════════════════════════════════════════════
  403. 2. ÉVOLUTION MOYENNE GÉNÉRALE
  404. null = séquence pas encore notée → pas de point tracé
  405. ══════════════════════════════════════════════════════ */
  406. if (document.getElementById('c-evol')) {
  407. new Chart('c-evol', {
  408. type: 'line',
  409. data: {
  410. labels: seqLabels,
  411. datasets: [{
  412. label: 'Moyenne générale',
  413. data : generalAvgBySeq,
  414. borderColor: '#0f6e56', backgroundColor: 'rgba(15,110,86,.08)',
  415. borderWidth: 2.5, pointBackgroundColor: '#0f6e56',
  416. pointRadius: generalAvgBySeq.map(v => v !== null ? 5 : 0),
  417. pointHoverRadius: 7, tension: .3, fill: true,
  418. spanGaps: false, // ne pas relier les points si null entre eux
  419. }]
  420. },
  421. options: {
  422. responsive: true,
  423. plugins: {
  424. legend: { display: false },
  425. tooltip: { ...TIP, callbacks: { label: c => c.raw !== null ? ' Moy. : ' + c.raw + '/20' : ' Pas d\'évaluation' } },
  426. },
  427. scales: {
  428. x: { ...AX() },
  429. y: { ...AX(), min: 0, max: 20, ticks: { ...AX().ticks, stepSize: 5 } },
  430. },
  431. },
  432. plugins: [{
  433. id: 'seuil',
  434. afterDraw(ch) {
  435. const { ctx, chartArea: { left, right }, scales: { y } } = ch;
  436. const yp = y.getPixelForValue(10);
  437. ctx.save();
  438. ctx.beginPath(); ctx.moveTo(left, yp); ctx.lineTo(right, yp);
  439. ctx.strokeStyle = 'rgba(163,45,45,.5)'; ctx.setLineDash([5, 4]);
  440. ctx.lineWidth = 1.5; ctx.stroke();
  441. ctx.fillStyle = 'rgba(163,45,45,.7)'; ctx.font = '11px DM Sans';
  442. ctx.fillText('Seuil 10', left + 4, yp - 5);
  443. ctx.restore();
  444. }
  445. }]
  446. });
  447. }
  448. /* ══════════════════════════════════════════════════════
  449. 3. RÉUSSITE VS ÉCHEC (barres empilées)
  450. Données depuis evaluation.success_h/f + faillures_h/f
  451. → pas depuis mark, déjà agrégées par le controller
  452. ══════════════════════════════════════════════════════ */
  453. if (document.getElementById('c-success') && successFailData.labels.length > 0) {
  454. new Chart('c-success', {
  455. type: 'bar',
  456. data: {
  457. labels: successFailData.labels,
  458. datasets: [
  459. {
  460. label: 'Réussite', data: successFailData.success,
  461. backgroundColor: 'rgba(59,109,17,.55)', borderColor: C.em,
  462. borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
  463. },
  464. {
  465. label: 'Échec', data: successFailData.failures,
  466. backgroundColor: 'rgba(163,45,45,.55)', borderColor: C.rose,
  467. borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
  468. },
  469. {
  470. label: 'Absents', data: successFailData.absents,
  471. backgroundColor: 'rgba(133,79,11,.45)', borderColor: C.amber,
  472. borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
  473. },
  474. ],
  475. },
  476. options: {
  477. responsive: true,
  478. interaction: { mode: 'index', intersect: false },
  479. plugins: {
  480. legend: { position: 'top', align: 'end', labels: { boxWidth: 9, boxHeight: 9, borderRadius: 3, useBorderRadius: true, color: C.txt, padding: 12 } },
  481. tooltip: { ...TIP },
  482. },
  483. scales: {
  484. x: { ...AX(), stacked: true, ticks: { ...AX().ticks, maxRotation: 35 } },
  485. y: { ...AX(), stacked: true, beginAtZero: true },
  486. },
  487. },
  488. });
  489. }
  490. /* ══════════════════════════════════════════════════════
  491. 4. RADAR PAR TRIMESTRE
  492. Révèle le profil de la classe : quelle matière est
  493. forte/faible, stable ou en progression entre trimestres
  494. ══════════════════════════════════════════════════════ */
  495. if (document.getElementById('c-radar') && quaterProfiles.length > 0) {
  496. new Chart('c-radar', {
  497. type: 'radar',
  498. data: {
  499. labels: courseLabels,
  500. datasets: quaterProfiles.map((qp, i) => ({
  501. label : qp.label,
  502. data : qp.data,
  503. backgroundColor : PALQ[i % PALQ.length],
  504. borderColor : PALQ_BD[i % PALQ_BD.length],
  505. borderWidth : 2,
  506. pointBackgroundColor: PALQ_BD[i % PALQ_BD.length],
  507. pointRadius : 4,
  508. spanGaps : true,
  509. })),
  510. },
  511. options: {
  512. responsive: true,
  513. plugins: {
  514. legend: {
  515. position: 'top', align: 'end',
  516. labels: { boxWidth: 9, boxHeight: 9, borderRadius: 3, useBorderRadius: true, color: C.txt, padding: 12 }
  517. },
  518. tooltip: { ...TIP, callbacks: { label: c => ' ' + c.dataset.label + ' : ' + (c.raw !== null ? c.raw + '/20' : '—') } },
  519. },
  520. scales: {
  521. r: {
  522. min: 0, max: 20,
  523. backgroundColor: 'rgba(24,95,165,.04)',
  524. grid : { color: C.grid },
  525. angleLines : { color: C.grid },
  526. ticks : { color: C.dim, backdropColor: 'transparent', stepSize: 5 },
  527. pointLabels : { color: C.dim, font: { size: 11 } },
  528. },
  529. },
  530. },
  531. });
  532. }
  533. /* ══════════════════════════════════════════════════════
  534. 5. DONUT EXAMENS OFFICIELS (seulement si classe examen)
  535. ══════════════════════════════════════════════════════ */
  536. {% if classroom.apc %}
  537. if (document.getElementById('c-mentions') && mentionCountCategories.length > 0) {
  538. new Chart('c-mentions', {
  539. type: 'doughnut',
  540. data: {
  541. labels: mentionCategories,
  542. datasets: [{
  543. data: mentionCountCategories,
  544. backgroundColor: PAL.slice(0, mentionCategories.length),
  545. borderColor: '#f0f4f8', borderWidth: 3, hoverOffset: 8,
  546. }],
  547. },
  548. options: {
  549. responsive: true, cutout: '58%',
  550. plugins: {
  551. legend: { position: 'bottom', labels: { boxWidth: 8, boxHeight: 8, borderRadius: 2, useBorderRadius: true, color: C.txt, padding: 8, font: { size: 11 } } },
  552. tooltip: { ...TIP, callbacks: { label: c => ' ' + c.label + ' : ' + c.raw } },
  553. },
  554. },
  555. });
  556. }
  557. {% endif %}
  558. /* ── Modales actions ──────────────────────────────────── */
  559. $(document).ready(function () {
  560. $('#form_modal_courses').on('show.bs.modal', function (e) {
  561. $('#form_modal_courses form').attr('action', $(e.relatedTarget).data('action'));
  562. });
  563. $('[data-toggle="modal"]').on('click', function () {
  564. $('#form_modal_reportcard_params form').attr('action', $(this).data('action'));
  565. });
  566. });
  567. })();
  568. </script>
  569. {% endblock %}