templates/classroom/index.html.twig line 1

Open in your IDE?
  1. {% extends 'layout/backEndLayout.html.twig' %}
  2. {% block stylesheets %}
  3. {{ parent() }}
  4. <style>
  5. /* ── Tokens locaux ─────────────────────────────────────────── */
  6. :root {
  7. --ci-bg : #f5f7fa;
  8. --ci-card : #ffffff;
  9. --ci-border : rgba(0,0,0,.08);
  10. --ci-border-hi: rgba(0,0,0,.16);
  11. --ci-ink : #111827;
  12. --ci-dim : #4b5563;
  13. --ci-muted : #9ca3af;
  14. --ci-blue : #1d4ed8;
  15. --ci-blue-lt : rgba(29,78,216,.1);
  16. --ci-teal : #0d9488;
  17. --ci-teal-lt : rgba(13,148,136,.1);
  18. --ci-amber : #b45309;
  19. --ci-amber-lt : rgba(180,83,9,.1);
  20. --ci-rose : #be123c;
  21. --ci-rose-lt : rgba(190,18,60,.1);
  22. --ci-r : 12px;
  23. --ci-rs : 8px;
  24. --ci-shadow : 0 1px 4px rgba(0,0,0,.06), 0 4px 16px rgba(0,0,0,.06);
  25. }
  26. /* ── Layout page ───────────────────────────────────────────── */
  27. .ci-page { font-family:'DM Sans','Segoe UI',sans-serif; }
  28. /* ── Barre de tête ─────────────────────────────────────────── */
  29. .ci-topbar {
  30. display: flex;
  31. align-items: center;
  32. justify-content: space-between;
  33. flex-wrap: wrap;
  34. gap: 12px;
  35. margin-bottom: 24px;
  36. }
  37. .ci-topbar__title {
  38. font-size: 20px;
  39. font-weight: 700;
  40. color: var(--ci-ink);
  41. display: flex;
  42. align-items: center;
  43. gap: 10px;
  44. }
  45. .ci-topbar__title i { color: var(--ci-blue); font-size: 18px; }
  46. .ci-topbar__sub { font-size: 13px; color: var(--ci-muted); margin-top: 2px; }
  47. /* ── Statistiques globales ─────────────────────────────────── */
  48. .ci-stats {
  49. display: grid;
  50. grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  51. gap: 12px;
  52. margin-bottom: 24px;
  53. }
  54. .ci-stat {
  55. background: var(--ci-card);
  56. border: 1px solid var(--ci-border);
  57. border-radius: var(--ci-r);
  58. padding: 16px;
  59. display: flex;
  60. align-items: center;
  61. gap: 12px;
  62. transition: box-shadow .2s;
  63. }
  64. .ci-stat:hover { box-shadow: var(--ci-shadow); }
  65. .ci-stat__icon {
  66. width: 40px; height: 40px;
  67. border-radius: var(--ci-rs);
  68. display: flex; align-items: center; justify-content: center;
  69. font-size: 16px; flex-shrink: 0;
  70. }
  71. .ic-blue { background: var(--ci-blue-lt); color: var(--ci-blue); }
  72. .ic-teal { background: var(--ci-teal-lt); color: var(--ci-teal); }
  73. .ic-amber { background: var(--ci-amber-lt); color: var(--ci-amber); }
  74. .ic-rose { background: var(--ci-rose-lt); color: var(--ci-rose); }
  75. .ci-stat__val { font-size: 22px; font-weight: 700; color: var(--ci-ink); line-height: 1; }
  76. .ci-stat__lbl { font-size: 11.5px; color: var(--ci-muted); margin-top: 2px; }
  77. /* ── Tableau ───────────────────────────────────────────────── */
  78. .ci-table-card {
  79. background: var(--ci-card);
  80. border: 1px solid var(--ci-border);
  81. border-radius: var(--ci-r);
  82. overflow: hidden;
  83. box-shadow: var(--ci-shadow);
  84. }
  85. .ci-table-head {
  86. display: flex;
  87. align-items: center;
  88. justify-content: space-between;
  89. flex-wrap: wrap;
  90. gap: 10px;
  91. padding: 16px 20px;
  92. border-bottom: 1px solid var(--ci-border);
  93. background: #fafbfc;
  94. }
  95. .ci-table-head__title {
  96. font-size: 14px;
  97. font-weight: 600;
  98. color: var(--ci-ink);
  99. display: flex;
  100. align-items: center;
  101. gap: 8px;
  102. }
  103. .ci-table-head__dot {
  104. width: 8px; height: 8px;
  105. border-radius: 50%;
  106. background: var(--ci-blue);
  107. box-shadow: 0 0 6px var(--ci-blue);
  108. }
  109. /* Bouton ajouter */
  110. .btn-add {
  111. display: inline-flex;
  112. align-items: center;
  113. gap: 6px;
  114. padding: 8px 16px;
  115. background: var(--ci-blue);
  116. color: #fff;
  117. border-radius: var(--ci-rs);
  118. font-size: 13px;
  119. font-weight: 600;
  120. text-decoration: none;
  121. border: none;
  122. cursor: pointer;
  123. transition: background .18s, transform .14s;
  124. }
  125. .btn-add:hover { background: #1e40af; color: #fff; transform: translateY(-1px); }
  126. /* Table */
  127. table.ci-tbl {
  128. width: 100%;
  129. border-collapse: collapse;
  130. font-size: 13.5px;
  131. }
  132. table.ci-tbl thead th {
  133. padding: 10px 16px;
  134. color: var(--ci-muted);
  135. font-size: 11px;
  136. font-weight: 600;
  137. text-transform: uppercase;
  138. letter-spacing: .06em;
  139. background: #fafbfc;
  140. border-bottom: 1px solid var(--ci-border);
  141. white-space: nowrap;
  142. }
  143. table.ci-tbl tbody td {
  144. padding: 12px 16px;
  145. border-bottom: 1px solid rgba(0,0,0,.04);
  146. color: var(--ci-ink);
  147. vertical-align: middle;
  148. }
  149. table.ci-tbl tbody tr:last-child td { border-bottom: none; }
  150. table.ci-tbl tbody tr { transition: background .12s; }
  151. table.ci-tbl tbody tr:hover td { background: rgba(29,78,216,.03); }
  152. /* Nom de la classe */
  153. .room-name {
  154. font-weight: 700;
  155. font-size: 14px;
  156. color: var(--ci-ink);
  157. }
  158. .room-link {
  159. display: inline-flex;
  160. align-items: center;
  161. gap: 6px;
  162. color: var(--ci-ink);
  163. text-decoration: none;
  164. }
  165. .room-link:hover { color: var(--ci-blue); }
  166. .room-link:hover .room-name { text-decoration: underline; }
  167. /* Niveau badge */
  168. .level-badge {
  169. display: inline-block;
  170. padding: 3px 10px;
  171. border-radius: 20px;
  172. font-size: 11.5px;
  173. font-weight: 600;
  174. background: var(--ci-blue-lt);
  175. color: var(--ci-blue);
  176. border: 1px solid rgba(29,78,216,.2);
  177. }
  178. /* Séquence active */
  179. .seq-badge {
  180. display: inline-flex;
  181. align-items: center;
  182. gap: 5px;
  183. padding: 3px 10px;
  184. border-radius: 20px;
  185. font-size: 11px;
  186. font-weight: 500;
  187. background: var(--ci-teal-lt);
  188. color: var(--ci-teal);
  189. border: 1px solid rgba(13,148,136,.2);
  190. }
  191. .seq-dot {
  192. width: 6px; height: 6px;
  193. border-radius: 50%;
  194. background: var(--ci-teal);
  195. animation: pulse-dot 1.6s ease infinite;
  196. }
  197. @keyframes pulse-dot {
  198. 0%,100% { opacity:1; } 50% { opacity:.35; }
  199. }
  200. /* Prof titulaire */
  201. .main-teacher {
  202. display: flex;
  203. align-items: center;
  204. gap: 7px;
  205. font-size: 13px;
  206. }
  207. .teacher-avatar {
  208. width: 26px; height: 26px;
  209. border-radius: 50%;
  210. background: linear-gradient(135deg, #1d4ed8, #0d9488);
  211. color: #fff;
  212. font-size: 10px;
  213. font-weight: 700;
  214. display: flex;
  215. align-items: center;
  216. justify-content: center;
  217. flex-shrink: 0;
  218. }
  219. .no-teacher {
  220. font-size: 12px;
  221. color: var(--ci-rose);
  222. font-style: italic;
  223. }
  224. /* Boutons action ligne */
  225. .ci-actions {
  226. display: flex;
  227. align-items: center;
  228. gap: 4px;
  229. justify-content: flex-end;
  230. }
  231. .ci-btn {
  232. display: inline-flex;
  233. align-items: center;
  234. gap: 5px;
  235. padding: 5px 10px;
  236. border-radius: 7px;
  237. font-size: 11.5px;
  238. font-weight: 500;
  239. text-decoration: none;
  240. border: 1px solid var(--ci-border);
  241. cursor: pointer;
  242. background: #fff;
  243. color: var(--ci-dim);
  244. transition: background .15s, border-color .15s, color .15s;
  245. white-space: nowrap;
  246. }
  247. .ci-btn:hover { border-color: var(--ci-border-hi); color: var(--ci-ink); background: #f8f9fa; }
  248. .ci-btn-primary { background: var(--ci-blue-lt); color: var(--ci-blue); border-color: rgba(29,78,216,.25); }
  249. .ci-btn-primary:hover { background: #1d4ed8; color: #fff; }
  250. .ci-btn-warning { background: var(--ci-amber-lt); color: var(--ci-amber); border-color: rgba(180,83,9,.25); }
  251. .ci-btn-warning:hover { background: #b45309; color: #fff; }
  252. .ci-btn-danger { background: var(--ci-rose-lt); color: var(--ci-rose); border-color: rgba(190,18,60,.25); }
  253. .ci-btn-danger:hover { background: #be123c; color: #fff; }
  254. /* ── Pagination ────────────────────────────────────────────── */
  255. .ci-pagination {
  256. display: flex;
  257. align-items: center;
  258. justify-content: space-between;
  259. flex-wrap: wrap;
  260. gap: 10px;
  261. padding: 14px 20px;
  262. border-top: 1px solid var(--ci-border);
  263. background: #fafbfc;
  264. }
  265. .ci-pagination__info { font-size: 12.5px; color: var(--ci-muted); }
  266. .ci-pager {
  267. display: flex;
  268. align-items: center;
  269. gap: 4px;
  270. list-style: none;
  271. margin: 0; padding: 0;
  272. }
  273. .ci-pager li a,
  274. .ci-pager li span {
  275. display: inline-flex;
  276. align-items: center;
  277. justify-content: center;
  278. min-width: 34px;
  279. height: 34px;
  280. padding: 0 8px;
  281. border-radius: 8px;
  282. border: 1px solid var(--ci-border);
  283. font-size: 13px;
  284. font-weight: 500;
  285. color: var(--ci-dim);
  286. text-decoration: none;
  287. background: #fff;
  288. transition: background .15s, border-color .15s, color .15s;
  289. }
  290. .ci-pager li a:hover { border-color: var(--ci-blue); color: var(--ci-blue); }
  291. .ci-pager li.active span {
  292. background: var(--ci-blue);
  293. color: #fff;
  294. border-color: var(--ci-blue);
  295. font-weight: 700;
  296. }
  297. .ci-pager li.disabled span { opacity: .4; cursor: default; }
  298. /* ── Vide ──────────────────────────────────────────────────── */
  299. .ci-empty {
  300. text-align: center;
  301. padding: 64px 24px;
  302. color: var(--ci-muted);
  303. }
  304. .ci-empty i { font-size: 40px; margin-bottom: 12px; display: block; }
  305. .ci-empty strong { display: block; font-size: 15px; color: var(--ci-dim); margin-bottom: 4px; }
  306. /* ── Animation ─────────────────────────────────────────────── */
  307. @keyframes fadeUp {
  308. from { opacity:0; transform:translateY(10px); }
  309. to { opacity:1; transform:none; }
  310. }
  311. .ci-table-card { animation: fadeUp .4s ease both; }
  312. </style>
  313. {% endblock %}
  314. {% block content %}
  315. <div class="ci-page">
  316. {# ── Barre de tête ──────────────────────────────────────────── #}
  317. <div class="ci-topbar">
  318. <div>
  319. <div class="ci-topbar__title">
  320. <i class="fa fa-th-large"></i>
  321. Classes
  322. </div>
  323. <div class="ci-topbar__sub">
  324. Gestion des classes — {% if year %}Année scolaire {{ year.code }}{% endif %}
  325. {% if seq > 0 %} · Séquence active{% endif %}
  326. </div>
  327. </div>
  328. {% if is_granted('ROLE_ADMIN') %}
  329. <a href="{{ path('admin_classrooms_new') }}" class="btn-add">
  330. <i class="fa fa-plus"></i> Nouvelle classe
  331. </a>
  332. {% endif %}
  333. </div>
  334. {# ── KPIs globaux ───────────────────────────────────────────── #}
  335. <div class="ci-stats">
  336. <div class="ci-stat">
  337. <div class="ci-stat__icon ic-blue"><i class="fa fa-home"></i></div>
  338. <div>
  339. <div class="ci-stat__val">{{ classrooms|length }}</div>
  340. <div class="ci-stat__lbl">Classes</div>
  341. </div>
  342. </div>
  343. <div class="ci-stat">
  344. <div class="ci-stat__icon ic-teal"><i class="fa fa-graduation-cap"></i></div>
  345. <div>
  346. <div class="ci-stat__val">{{ students_count() }}</div>
  347. <div class="ci-stat__lbl">Élèves inscrits</div>
  348. </div>
  349. </div>
  350. <div class="ci-stat">
  351. <div class="ci-stat__icon ic-amber"><i class="fa fa-users"></i></div>
  352. <div>
  353. <div class="ci-stat__val">{{ teachers_count() }}</div>
  354. <div class="ci-stat__lbl">Enseignants</div>
  355. </div>
  356. </div>
  357. <div class="ci-stat">
  358. <div class="ci-stat__icon ic-rose"><i class="fa fa-book"></i></div>
  359. <div>
  360. <div class="ci-stat__val">{{ rooms_count() }}</div>
  361. <div class="ci-stat__lbl">Salles actives</div>
  362. </div>
  363. </div>
  364. </div>
  365. {# ── Tableau des classes ─────────────────────────────────────── #}
  366. {% set items_per_page = 12 %}
  367. {% set current_page = app.request.query.get('page', 1)|number_format %}
  368. {% set total_pages = (classrooms|length / items_per_page)|round(0, 'ceil') %}
  369. <div class="ci-table-card">
  370. <div class="ci-table-head">
  371. <div class="ci-table-head__title">
  372. <span class="ci-table-head__dot"></span>
  373. Liste des classes
  374. <span style="font-size:11.5px;color:var(--ci-muted);font-weight:400;">({{ classrooms|length }} au total)</span>
  375. </div>
  376. <div style="display:flex;gap:8px;align-items:center;">
  377. <span class="seq-badge">
  378. <span class="seq-dot"></span>
  379. {% if seq > 0 %}Séquence active{% else %}Aucune séquence active{% endif %}
  380. </span>
  381. </div>
  382. </div>
  383. {% if classrooms is empty %}
  384. <div class="ci-empty">
  385. <i class="fa fa-inbox"></i>
  386. <strong>Aucune classe enregistrée</strong>
  387. Commencez par créer une classe.
  388. </div>
  389. {% else %}
  390. <div style="overflow-x:auto;">
  391. <table class="ci-tbl">
  392. <thead>
  393. <tr>
  394. <th style="width:4%">#</th>
  395. <th style="width:18%">Classe</th>
  396. <th style="width:12%">Niveau</th>
  397. <th style="width:22%">Professeur titulaire</th>
  398. <th style="width:10%" class="text-center">Modules</th>
  399. <th style="width:10%" class="text-center">Matières</th>
  400. <th style="width:24%" class="text-right">Actions</th>
  401. </tr>
  402. </thead>
  403. <tbody>
  404. {% for classroom in classrooms %}
  405. {# ── Pagination côté Twig ── #}
  406. {% if loop.index > (current_page - 1) * items_per_page and loop.index <= current_page * items_per_page %}
  407. <tr>
  408. <td style="color:var(--ci-muted);font-size:12px">{{ (current_page - 1) * items_per_page + loop.index - ((current_page - 1) * items_per_page) }}</td>
  409. <td>
  410. <a href="{{ path('admin_classrooms_show', {id: classroom.id}) }}" class="room-link">
  411. <span class="room-name">{{ classroom.name }}</span>
  412. </a>
  413. </td>
  414. <td>
  415. <span class="level-badge">{{ classroom.level }}</span>
  416. </td>
  417. <td>
  418. {% if mainTeachers[classroom.id] is defined %}
  419. <div class="main-teacher">
  420. <div class="teacher-avatar">
  421. {{ mainTeachers[classroom.id].fullName|slice(0,1)|upper }}{{ mainTeachers[classroom.id].fullName|split(' ')|last|slice(0,1)|upper }}
  422. </div>
  423. {{ mainTeachers[classroom.id].fullName }}
  424. </div>
  425. {% else %}
  426. <span class="no-teacher"><i class="fa fa-exclamation-triangle"></i> Non assigné</span>
  427. {% endif %}
  428. </td>
  429. <td class="text-center">
  430. <span style="font-weight:600;color:var(--ci-blue)">{{ classroom.modules|length }}</span>
  431. </td>
  432. <td class="text-center">
  433. {% set total_courses = 0 %}
  434. {% for module in classroom.modules %}
  435. {% set total_courses = total_courses + module.courses|length %}
  436. {% endfor %}
  437. <span style="font-weight:600;color:var(--ci-teal)">{{ total_courses }}</span>
  438. </td>
  439. <td>
  440. <div class="ci-actions">
  441. <a class="ci-btn ci-btn-primary" href="{{ path('admin_classrooms_show', {id: classroom.id}) }}" title="Voir">
  442. <i class="fa fa-eye"></i> Voir
  443. </a>
  444. {% if is_granted('ROLE_ADMIN') %}
  445. <a class="ci-btn ci-btn-warning" href="{{ path('admin_classrooms_edit', {id: classroom.id}) }}" title="Modifier">
  446. <i class="fa fa-pencil"></i>
  447. </a>
  448. {% if seq > 0 %}
  449. <a class="ci-btn" href="{{ path('admin_classrooms_recapitulatif', {room: classroom.id, seq: seq}) }}" title="Récapitulatif PDF">
  450. <i class="fa fa-file-pdf-o"></i>
  451. </a>
  452. {% endif %}
  453. <form method="post" action="{{ path('admin_classrooms_delete', {id: classroom.id}) }}" onsubmit="return confirm('Supprimer cette classe ?')" style="display:inline">
  454. <input type="hidden" name="_method" value="DELETE">
  455. <button class="ci-btn ci-btn-danger" type="submit" title="Supprimer">
  456. <i class="fa fa-trash"></i>
  457. </button>
  458. </form>
  459. {% endif %}
  460. </div>
  461. </td>
  462. </tr>
  463. {% endif %}
  464. {% endfor %}
  465. </tbody>
  466. </table>
  467. </div>
  468. {# ── Pagination ────────────────────────────────────────────── #}
  469. {% if total_pages > 1 %}
  470. <div class="ci-pagination">
  471. <div class="ci-pagination__info">
  472. Page {{ current_page }} sur {{ total_pages }} — {{ classrooms|length }} classe{{ classrooms|length > 1 ? 's' : '' }}
  473. </div>
  474. <ul class="ci-pager">
  475. <li class="{{ current_page <= 1 ? 'disabled' : '' }}">
  476. {% if current_page > 1 %}
  477. <a href="?page={{ current_page - 1 }}"><i class="fa fa-chevron-left"></i></a>
  478. {% else %}
  479. <span><i class="fa fa-chevron-left"></i></span>
  480. {% endif %}
  481. </li>
  482. {% for p in 1..total_pages %}
  483. {% if p == current_page %}
  484. <li class="active"><span>{{ p }}</span></li>
  485. {% elseif p == 1 or p == total_pages or (p >= current_page - 2 and p <= current_page + 2) %}
  486. <li><a href="?page={{ p }}">{{ p }}</a></li>
  487. {% elseif p == current_page - 3 or p == current_page + 3 %}
  488. <li><span style="border:none;background:transparent">…</span></li>
  489. {% endif %}
  490. {% endfor %}
  491. <li class="{{ current_page >= total_pages ? 'disabled' : '' }}">
  492. {% if current_page < total_pages %}
  493. <a href="?page={{ current_page + 1 }}"><i class="fa fa-chevron-right"></i></a>
  494. {% else %}
  495. <span><i class="fa fa-chevron-right"></i></span>
  496. {% endif %}
  497. </li>
  498. </ul>
  499. </div>
  500. {% else %}
  501. <div class="ci-pagination">
  502. <div class="ci-pagination__info">{{ classrooms|length }} classe{{ classrooms|length > 1 ? 's' : '' }} au total</div>
  503. </div>
  504. {% endif %}
  505. {% endif %}
  506. </div>
  507. </div>
  508. {% endblock %}