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-group mb-3">
  305. <label>Taille des caractères (entête)</label>
  306. <input type="range" class="form-control" name="header_font_size" min="1" max="1.6" step="0.01">
  307. </div>
  308. <div class="form-group mb-3">
  309. <label>Hauteur des lignes</label>
  310. <input type="range" class="form-control" name="line_height" min="0.5" max="1.5" step="0.01">
  311. </div>
  312. <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>
  313. <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>
  314. </div>
  315. <div class="modal-footer border-top-0 d-flex justify-content-center">
  316. <button type="submit" class="btn-cr btn-cr-blue">Générer</button>
  317. </div>
  318. </form>
  319. </div>
  320. </div>
  321. </div>
  322. {% endblock %}
  323. {% block javascripts %}
  324. {{ parent() }}
  325. <script>
  326. (function(){
  327. 'use strict';
  328. /* ── Données depuis le controller ─────────────────────── */
  329. const courseLabels = {{ courseLabels | raw }};
  330. const seqLabels = {{ seqLabels | raw }};
  331. const seqIds = {{ seqIds | raw }};
  332. const heatmapData = {{ heatmapData | raw }};
  333. const generalAvgBySeq = {{ generalAvgBySeq | raw }};
  334. const successFailData = {{ successFailData | raw }};
  335. const quaterProfiles = {{ quaterProfiles | raw }};
  336. {% if classroom.apc %}
  337. const mentionCategories = {{ mentionCategories | raw }};
  338. const mentionCountCategories = {{ mentionCountCategories | raw }};
  339. {% endif %}
  340. /* ── Palette & defaults ───────────────────────────────── */
  341. const C = {
  342. blue:'#185fa5', rose:'#a32d2d', amber:'#854f0b',
  343. teal:'#0f6e56', vi:'#534ab7', em:'#3b6d11', orange:'#9a4f0b',
  344. grid:'rgba(0,0,0,.07)', bg:'#ffffff', txt:'#1a2535', dim:'#4a5568'
  345. };
  346. const PALQ = ['rgba(24,95,165,.35)','rgba(15,110,86,.35)','rgba(83,74,183,.35)'];
  347. const PALQ_BD = ['#185fa5', '#0f6e56', '#534ab7'];
  348. const PAL = ['#3b82f6','#f59e0b','#f43f5e','#10b981','#8b5cf6','#14b8a6','#f97316','#84cc16'];
  349. Chart.defaults.color = C.dim;
  350. Chart.defaults.font.family = "'DM Sans','Segoe UI',sans-serif";
  351. Chart.defaults.font.size = 12;
  352. const TIP = {backgroundColor:'#ffffff',borderColor:'rgba(0,0,0,.12)',borderWidth:1,titleColor:C.txt,bodyColor:C.dim,padding:12,cornerRadius:10};
  353. const AX = (o={}) => ({grid:{color:C.grid,drawBorder:false},ticks:{color:C.dim},border:{display:false},...o});
  354. /* ══════════════════════════════════════════════════════
  355. 1. HEATMAP — table HTML colorée cours × séquences
  356. Résout le problème de départ : chaque cellule est
  357. indépendante. null = gris "pas d'éval enregistrée".
  358. ══════════════════════════════════════════════════════ */
  359. (function buildHeatmap(){
  360. const thead = document.querySelector('#heatmap-table thead tr');
  361. const tbody = document.getElementById('heatmap-body');
  362. // En-têtes séquences
  363. seqLabels.forEach(lbl => {
  364. const th = document.createElement('th');
  365. th.textContent = lbl;
  366. thead.appendChild(th);
  367. });
  368. // Colonne moyenne
  369. const thAvg = document.createElement('th');
  370. thAvg.textContent = 'Moy.';
  371. thead.appendChild(thAvg);
  372. // Lignes de données
  373. heatmapData.forEach(row => {
  374. const tr = document.createElement('tr');
  375. // Cellule nom de la matière
  376. const tdName = document.createElement('td');
  377. tdName.className = 'course-td';
  378. tdName.textContent = row.course;
  379. tr.appendChild(tdName);
  380. let sum = 0, cnt = 0;
  381. // Cellules par séquence
  382. seqIds.forEach(sid => {
  383. const val = row['s' + sid];
  384. const td = document.createElement('td');
  385. if (val === null || val === undefined) {
  386. td.className = 'hm-null';
  387. td.textContent = '—';
  388. } else {
  389. td.className = val < 10 ? 'hm-red' : val < 14 ? 'hm-yellow' : 'hm-green';
  390. td.textContent = val.toFixed(1);
  391. sum += val; cnt++;
  392. }
  393. tr.appendChild(td);
  394. });
  395. // Cellule moyenne
  396. const tdAvg = document.createElement('td');
  397. if (cnt > 0) {
  398. const avg = sum / cnt;
  399. tdAvg.className = avg < 10 ? 'hm-red' : avg < 14 ? 'hm-yellow' : 'hm-green';
  400. tdAvg.textContent = avg.toFixed(1);
  401. tdAvg.style.fontWeight = '700';
  402. } else {
  403. tdAvg.className = 'hm-null';
  404. tdAvg.textContent = '—';
  405. }
  406. tr.appendChild(tdAvg);
  407. tbody.appendChild(tr);
  408. });
  409. })();
  410. /* ══════════════════════════════════════════════════════
  411. 2. ÉVOLUTION MOYENNE GÉNÉRALE
  412. null = séquence pas encore notée → pas de point tracé
  413. ══════════════════════════════════════════════════════ */
  414. if (document.getElementById('c-evol')) {
  415. new Chart('c-evol', {
  416. type: 'line',
  417. data: {
  418. labels: seqLabels,
  419. datasets: [{
  420. label: 'Moyenne générale',
  421. data : generalAvgBySeq,
  422. borderColor: '#0f6e56', backgroundColor: 'rgba(15,110,86,.08)',
  423. borderWidth: 2.5, pointBackgroundColor: '#0f6e56',
  424. pointRadius: generalAvgBySeq.map(v => v !== null ? 5 : 0),
  425. pointHoverRadius: 7, tension: .3, fill: true,
  426. spanGaps: false, // ne pas relier les points si null entre eux
  427. }]
  428. },
  429. options: {
  430. responsive: true,
  431. plugins: {
  432. legend: { display: false },
  433. tooltip: { ...TIP, callbacks: { label: c => c.raw !== null ? ' Moy. : ' + c.raw + '/20' : ' Pas d\'évaluation' } },
  434. },
  435. scales: {
  436. x: { ...AX() },
  437. y: { ...AX(), min: 0, max: 20, ticks: { ...AX().ticks, stepSize: 5 } },
  438. },
  439. },
  440. plugins: [{
  441. id: 'seuil',
  442. afterDraw(ch) {
  443. const { ctx, chartArea: { left, right }, scales: { y } } = ch;
  444. const yp = y.getPixelForValue(10);
  445. ctx.save();
  446. ctx.beginPath(); ctx.moveTo(left, yp); ctx.lineTo(right, yp);
  447. ctx.strokeStyle = 'rgba(163,45,45,.5)'; ctx.setLineDash([5, 4]);
  448. ctx.lineWidth = 1.5; ctx.stroke();
  449. ctx.fillStyle = 'rgba(163,45,45,.7)'; ctx.font = '11px DM Sans';
  450. ctx.fillText('Seuil 10', left + 4, yp - 5);
  451. ctx.restore();
  452. }
  453. }]
  454. });
  455. }
  456. /* ══════════════════════════════════════════════════════
  457. 3. RÉUSSITE VS ÉCHEC (barres empilées)
  458. Données depuis evaluation.success_h/f + faillures_h/f
  459. → pas depuis mark, déjà agrégées par le controller
  460. ══════════════════════════════════════════════════════ */
  461. if (document.getElementById('c-success') && successFailData.labels.length > 0) {
  462. new Chart('c-success', {
  463. type: 'bar',
  464. data: {
  465. labels: successFailData.labels,
  466. datasets: [
  467. {
  468. label: 'Réussite', data: successFailData.success,
  469. backgroundColor: 'rgba(59,109,17,.55)', borderColor: C.em,
  470. borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
  471. },
  472. {
  473. label: 'Échec', data: successFailData.failures,
  474. backgroundColor: 'rgba(163,45,45,.55)', borderColor: C.rose,
  475. borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
  476. },
  477. {
  478. label: 'Absents', data: successFailData.absents,
  479. backgroundColor: 'rgba(133,79,11,.45)', borderColor: C.amber,
  480. borderWidth: 1.5, borderRadius: 4, borderSkipped: false,
  481. },
  482. ],
  483. },
  484. options: {
  485. responsive: true,
  486. interaction: { mode: 'index', intersect: false },
  487. plugins: {
  488. legend: { position: 'top', align: 'end', labels: { boxWidth: 9, boxHeight: 9, borderRadius: 3, useBorderRadius: true, color: C.txt, padding: 12 } },
  489. tooltip: { ...TIP },
  490. },
  491. scales: {
  492. x: { ...AX(), stacked: true, ticks: { ...AX().ticks, maxRotation: 35 } },
  493. y: { ...AX(), stacked: true, beginAtZero: true },
  494. },
  495. },
  496. });
  497. }
  498. /* ══════════════════════════════════════════════════════
  499. 4. RADAR PAR TRIMESTRE
  500. Révèle le profil de la classe : quelle matière est
  501. forte/faible, stable ou en progression entre trimestres
  502. ══════════════════════════════════════════════════════ */
  503. if (document.getElementById('c-radar') && quaterProfiles.length > 0) {
  504. new Chart('c-radar', {
  505. type: 'radar',
  506. data: {
  507. labels: courseLabels,
  508. datasets: quaterProfiles.map((qp, i) => ({
  509. label : qp.label,
  510. data : qp.data,
  511. backgroundColor : PALQ[i % PALQ.length],
  512. borderColor : PALQ_BD[i % PALQ_BD.length],
  513. borderWidth : 2,
  514. pointBackgroundColor: PALQ_BD[i % PALQ_BD.length],
  515. pointRadius : 4,
  516. spanGaps : true,
  517. })),
  518. },
  519. options: {
  520. responsive: true,
  521. plugins: {
  522. legend: {
  523. position: 'top', align: 'end',
  524. labels: { boxWidth: 9, boxHeight: 9, borderRadius: 3, useBorderRadius: true, color: C.txt, padding: 12 }
  525. },
  526. tooltip: { ...TIP, callbacks: { label: c => ' ' + c.dataset.label + ' : ' + (c.raw !== null ? c.raw + '/20' : '—') } },
  527. },
  528. scales: {
  529. r: {
  530. min: 0, max: 20,
  531. backgroundColor: 'rgba(24,95,165,.04)',
  532. grid : { color: C.grid },
  533. angleLines : { color: C.grid },
  534. ticks : { color: C.dim, backdropColor: 'transparent', stepSize: 5 },
  535. pointLabels : { color: C.dim, font: { size: 11 } },
  536. },
  537. },
  538. },
  539. });
  540. }
  541. /* ══════════════════════════════════════════════════════
  542. 5. DONUT EXAMENS OFFICIELS (seulement si classe examen)
  543. ══════════════════════════════════════════════════════ */
  544. {% if classroom.apc %}
  545. if (document.getElementById('c-mentions') && mentionCountCategories.length > 0) {
  546. new Chart('c-mentions', {
  547. type: 'doughnut',
  548. data: {
  549. labels: mentionCategories,
  550. datasets: [{
  551. data: mentionCountCategories,
  552. backgroundColor: PAL.slice(0, mentionCategories.length),
  553. borderColor: '#f0f4f8', borderWidth: 3, hoverOffset: 8,
  554. }],
  555. },
  556. options: {
  557. responsive: true, cutout: '58%',
  558. plugins: {
  559. legend: { position: 'bottom', labels: { boxWidth: 8, boxHeight: 8, borderRadius: 2, useBorderRadius: true, color: C.txt, padding: 8, font: { size: 11 } } },
  560. tooltip: { ...TIP, callbacks: { label: c => ' ' + c.label + ' : ' + c.raw } },
  561. },
  562. },
  563. });
  564. }
  565. {% endif %}
  566. /* ── Modales actions ──────────────────────────────────── */
  567. $(document).ready(function () {
  568. $('#form_modal_courses').on('show.bs.modal', function (e) {
  569. $('#form_modal_courses form').attr('action', $(e.relatedTarget).data('action'));
  570. });
  571. $('[data-toggle="modal"]').on('click', function () {
  572. $('#form_modal_reportcard_params form').attr('action', $(this).data('action'));
  573. });
  574. });
  575. })();
  576. </script>
  577. {% endblock %}