templates/subscription/index.html.twig line 1

Open in your IDE?
  1. {% extends 'layout/backEndLayout.html.twig' %}
  2. {% block stylesheets %}
  3. {{ parent() }}
  4. <style>
  5. :root {
  6. --sb-card : #ffffff;
  7. --sb-border : rgba(0,0,0,.08);
  8. --sb-border-hi: rgba(0,0,0,.15);
  9. --sb-ink : #111827;
  10. --sb-dim : #4b5563;
  11. --sb-muted : #9ca3af;
  12. --sb-blue : #1d4ed8;
  13. --sb-blue-lt: rgba(29,78,216,.1);
  14. --sb-teal : #0d9488;
  15. --sb-teal-lt: rgba(13,148,136,.1);
  16. --sb-amber : #b45309;
  17. --sb-amber-lt:rgba(180,83,9,.1);
  18. --sb-rose : #be123c;
  19. --sb-rose-lt: rgba(190,18,60,.1);
  20. --sb-green : #15803d;
  21. --sb-green-lt:rgba(21,128,61,.1);
  22. --sb-violet : #7c3aed;
  23. --sb-violet-lt:rgba(124,58,237,.1);
  24. --sb-r : 12px; --sb-rs: 8px;
  25. --sb-shadow: 0 1px 4px rgba(0,0,0,.06), 0 6px 20px rgba(0,0,0,.07);
  26. }
  27. .sb-page { font-family:'DM Sans','Segoe UI',sans-serif; color:var(--sb-ink); }
  28. /* topbar */
  29. .sb-topbar { display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:20px; }
  30. .sb-topbar__title { font-size:20px;font-weight:700;display:flex;align-items:center;gap:10px; }
  31. .sb-topbar__title i { color:var(--sb-blue); }
  32. .sb-topbar__sub { font-size:13px;color:var(--sb-muted);margin-top:2px; }
  33. /* kpis */
  34. .sb-kpis { display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px;margin-bottom:20px; }
  35. .sb-kpi { background:var(--sb-card);border:1px solid var(--sb-border);border-radius:var(--sb-r);padding:14px 16px;display:flex;align-items:center;gap:12px;box-shadow:var(--sb-shadow);transition:transform .2s; }
  36. .sb-kpi:hover { transform:translateY(-2px); }
  37. .sb-kpi__icon { width:38px;height:38px;border-radius:var(--sb-rs);display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0; }
  38. .ki-blue { background:var(--sb-blue-lt);color:var(--sb-blue); }
  39. .ki-green { background:var(--sb-green-lt);color:var(--sb-green); }
  40. .ki-amber { background:var(--sb-amber-lt);color:var(--sb-amber); }
  41. .ki-violet { background:var(--sb-violet-lt);color:var(--sb-violet); }
  42. .sb-kpi__val { font-size:20px;font-weight:700;color:var(--sb-ink);line-height:1; }
  43. .sb-kpi__lbl { font-size:11.5px;color:var(--sb-muted);margin-top:2px; }
  44. /* table card */
  45. .sb-table-card { background:var(--sb-card);border:1px solid var(--sb-border);border-radius:var(--sb-r);overflow:hidden;box-shadow:var(--sb-shadow); }
  46. .sb-table-head { display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;padding:14px 20px;border-bottom:1px solid var(--sb-border);background:#fafbfc; }
  47. .sb-table-head__title { font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px; }
  48. .sb-dot { width:8px;height:8px;border-radius:50%;background:var(--sb-blue);box-shadow:0 0 6px var(--sb-blue); }
  49. /* bouton ajout */
  50. .btn-add { display:inline-flex;align-items:center;gap:6px;padding:8px 18px;background:var(--sb-blue);color:#fff;border-radius:var(--sb-rs);font-size:13px;font-weight:600;text-decoration:none;border:none;cursor:pointer;transition:background .18s; }
  51. .btn-add:hover { background:#1e40af;color:#fff; }
  52. /* table */
  53. table.sb-tbl { width:100%;border-collapse:collapse;font-size:13px; }
  54. table.sb-tbl thead th { padding:10px 14px;color:var(--sb-muted);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;background:#fafbfc;border-bottom:1px solid var(--sb-border);white-space:nowrap; }
  55. table.sb-tbl tbody td { padding:11px 14px;border-bottom:1px solid rgba(0,0,0,.04);vertical-align:middle; }
  56. table.sb-tbl tbody tr:last-child td { border-bottom:none; }
  57. table.sb-tbl tbody tr:hover td { background:rgba(29,78,216,.025); }
  58. /* student cell */
  59. .std-cell { display:flex;align-items:center;gap:9px; }
  60. .std-avatar { width:30px;height:30px;border-radius:50%;background:linear-gradient(135deg,#1d4ed8,#0d9488);color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0; }
  61. .std-name { font-weight:600;font-size:13px; }
  62. .std-mat { font-size:11px;color:var(--sb-muted); }
  63. /* chips */
  64. .chip { display:inline-block;padding:3px 10px;border-radius:20px;font-size:11.5px;font-weight:600; }
  65. .chip-teal { background:var(--sb-teal-lt);color:var(--sb-teal);border:1px solid rgba(13,148,136,.2); }
  66. .chip-violet { background:var(--sb-violet-lt);color:var(--sb-violet);border:1px solid rgba(124,58,237,.2); }
  67. /* montant */
  68. .amount-cell { font-weight:700;font-size:14px;color:var(--sb-blue); }
  69. /* résultat examen */
  70. .exam-chip { display:inline-block;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:600;border:1px solid; }
  71. .ec-success { background:rgba(21,128,61,.1);color:var(--sb-green);border-color:rgba(21,128,61,.25); }
  72. .ec-fail { background:var(--sb-rose-lt);color:var(--sb-rose);border-color:rgba(190,18,60,.25); }
  73. .ec-neutral { background:rgba(0,0,0,.05);color:var(--sb-muted);border-color:var(--sb-border); }
  74. /* actions */
  75. .row-actions { display:flex;align-items:center;gap:4px;justify-content:flex-end; }
  76. .ra-btn { display:inline-flex;align-items:center;gap:4px;padding:5px 9px;border-radius:7px;font-size:11.5px;font-weight:500;text-decoration:none;border:1px solid var(--sb-border);cursor:pointer;background:#fff;color:var(--sb-dim);transition:background .15s,border-color .15s,color .15s; }
  77. .ra-btn:hover { border-color:var(--sb-border-hi); }
  78. .ra-blue { background:var(--sb-blue-lt);color:var(--sb-blue);border-color:rgba(29,78,216,.25); } .ra-blue:hover { background:var(--sb-blue);color:#fff; }
  79. .ra-amber { background:var(--sb-amber-lt);color:var(--sb-amber);border-color:rgba(180,83,9,.25); } .ra-amber:hover { background:var(--sb-amber);color:#fff; }
  80. .ra-rose { background:var(--sb-rose-lt);color:var(--sb-rose);border-color:rgba(190,18,60,.25); } .ra-rose:hover { background:var(--sb-rose);color:#fff; }
  81. /* pagination */
  82. .pag-bar { display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;padding:14px 20px;border-top:1px solid var(--sb-border);background:#fafbfc; }
  83. .pag-info { font-size:12.5px;color:var(--sb-muted); }
  84. .pag-list { display:flex;align-items:center;gap:4px;list-style:none;margin:0;padding:0; }
  85. .pag-list li a,.pag-list li span { display:inline-flex;align-items:center;justify-content:center;min-width:34px;height:34px;padding:0 6px;border-radius:8px;border:1px solid var(--sb-border);font-size:13px;font-weight:500;color:var(--sb-dim);text-decoration:none;background:#fff;transition:background .15s,border-color .15s,color .15s; }
  86. .pag-list li a:hover { border-color:var(--sb-blue);color:var(--sb-blue); }
  87. .pag-list li.pg-on span { background:var(--sb-blue);color:#fff;border-color:var(--sb-blue);font-weight:700; }
  88. .pag-list li.pg-off span { opacity:.4;pointer-events:none; }
  89. .pag-list li.pg-dots span { border:none;background:transparent;color:var(--sb-muted);min-width:auto; }
  90. /* vide */
  91. .sb-empty { text-align:center;padding:64px 24px;color:var(--sb-muted); }
  92. .sb-empty i { font-size:40px;margin-bottom:12px;display:block; }
  93. .sb-empty strong { display:block;font-size:15px;color:var(--sb-dim);margin-bottom:6px; }
  94. @keyframes fadeUp { from{opacity:0;transform:translateY(10px)} to{opacity:1;transform:none} }
  95. .sb-table-card { animation:fadeUp .4s ease both; }
  96. </style>
  97. {% endblock %}
  98. {% block content %}
  99. <div class="sb-page">
  100. {# ── Topbar ─────────────────────────────────────────────────── #}
  101. <div class="sb-topbar">
  102. <div>
  103. <div class="sb-topbar__title"><i class="fa fa-exchange"></i> Inscriptions</div>
  104. <div class="sb-topbar__sub">
  105. Gestion des inscriptions{% if year is defined %} — Année {{ year.code }}{% endif %}
  106. </div>
  107. </div>
  108. {% if is_granted('ROLE_ADMIN') %}
  109. <a href="{{ path('admin_subscriptions_new') }}" class="btn-add">
  110. <i class="fa fa-plus"></i> Nouvelle inscription
  111. </a>
  112. {% endif %}
  113. </div>
  114. {# ── KPIs ────────────────────────────────────────────────────── #}
  115. {% set total = subscriptions.totalItemCount %}
  116. {% set total_amount = 0 %}
  117. {% for sub in subscriptions %}
  118. {% set total_amount = total_amount + sub.amount %}
  119. {% endfor %}
  120. <div class="sb-kpis">
  121. <div class="sb-kpi">
  122. <div class="sb-kpi__icon ki-blue"><i class="fa fa-exchange"></i></div>
  123. <div><div class="sb-kpi__val">{{ total }}</div><div class="sb-kpi__lbl">Total inscriptions</div></div>
  124. </div>
  125. <div class="sb-kpi">
  126. <div class="sb-kpi__icon ki-green"><i class="fa fa-money"></i></div>
  127. <div><div class="sb-kpi__val">{{ total_amount|number_format(0, ',', ' ') }}</div><div class="sb-kpi__lbl">Montant encaissé (page)</div></div>
  128. </div>
  129. <div class="sb-kpi">
  130. <div class="sb-kpi__icon ki-violet"><i class="fa fa-graduation-cap"></i></div>
  131. <div><div class="sb-kpi__val">{{ subscriptions.currentPageNumber }}/{{ subscriptions.pageCount }}</div><div class="sb-kpi__lbl">Page courante</div></div>
  132. </div>
  133. </div>
  134. {# ── Table ───────────────────────────────────────────────────── #}
  135. <div class="sb-table-card">
  136. <div class="sb-table-head">
  137. <div class="sb-table-head__title">
  138. <span class="sb-dot"></span>
  139. Liste des inscriptions
  140. <span style="font-size:11.5px;color:var(--sb-muted);font-weight:400">({{ total }} au total)</span>
  141. </div>
  142. <span style="font-size:12px;color:var(--sb-muted)">Page {{ subscriptions.currentPageNumber }}/{{ subscriptions.pageCount }}</span>
  143. </div>
  144. {% if total == 0 %}
  145. <div class="sb-empty">
  146. <i class="fa fa-inbox"></i>
  147. <strong>Aucune inscription enregistrée</strong>
  148. Commencez par inscrire un élève.
  149. </div>
  150. {% else %}
  151. <div style="overflow-x:auto">
  152. <table class="sb-tbl">
  153. <thead>
  154. <tr>
  155. <th style="width:4%">#</th>
  156. <th style="width:24%">Élève</th>
  157. <th style="width:14%">Classe</th>
  158. <th style="width:14%">Année scolaire</th>
  159. <th style="width:11%" class="text-right">Montant</th>
  160. <th style="width:14%" class="text-center">Résultat examen</th>
  161. <th style="width:12%" class="text-center">Date</th>
  162. <th style="width:10%" class="text-right">Actions</th>
  163. </tr>
  164. </thead>
  165. <tbody>
  166. {% set offset = (subscriptions.currentPageNumber - 1) * subscriptions.itemNumberPerPage %}
  167. {% for sub in subscriptions %}
  168. {% set initials = sub.student.lastname|slice(0,1)|upper ~ sub.student.firstname|slice(0,1)|upper %}
  169. {% set exam = sub.officialExamResult %}
  170. <tr>
  171. <td style="color:var(--sb-muted);font-size:12px">{{ offset + loop.index }}</td>
  172. <td>
  173. <div class="std-cell">
  174. <div class="std-avatar">{{ initials }}</div>
  175. <div>
  176. <div class="std-name">
  177. <a href="{{ path('admin_students_show', {id: sub.student.id}) }}"
  178. style="color:var(--sb-ink);text-decoration:none">
  179. {{ sub.student.lastname }} {{ sub.student.firstname }}
  180. </a>
  181. </div>
  182. <div class="std-mat">{{ sub.student.matricule }}</div>
  183. </div>
  184. </div>
  185. </td>
  186. <td>
  187. <a href="{{ path('admin_classrooms_show', {id: sub.classRoom.id}) }}" style="text-decoration:none">
  188. <span class="chip chip-teal">{{ sub.classRoom.name }}</span>
  189. </a>
  190. </td>
  191. <td>
  192. <span class="chip chip-violet">{{ sub.schoolYear.code }}</span>
  193. </td>
  194. <td class="text-right">
  195. <span class="amount-cell">{{ sub.amount|number_format(0, ',', ' ') }} F</span>
  196. </td>
  197. <td class="text-center">
  198. {% if exam == '0' %}
  199. <span class="exam-chip ec-fail">ÉCHEC</span>
  200. {% elseif exam starts with '1' %}
  201. <span class="exam-chip ec-success">{{ sub.verbalOfficialExamResult }}</span>
  202. {% else %}
  203. <span class="exam-chip ec-neutral">{{ sub.verbalOfficialExamResult }}</span>
  204. {% endif %}
  205. </td>
  206. <td class="text-center" style="font-size:12px;color:var(--sb-dim)">
  207. {% if sub.createdAt is defined and sub.createdAt %}
  208. {{ sub.createdAt|date('d/m/Y') }}
  209. {% else %}—{% endif %}
  210. </td>
  211. <td>
  212. <div class="row-actions">
  213. <a class="ra-btn ra-blue" href="{{ path('admin_subscriptions_show', {id: sub.id}) }}" title="Voir">
  214. <i class="fa fa-eye"></i>
  215. </a>
  216. {% if is_granted('ROLE_ADMIN') %}
  217. <a class="ra-btn ra-amber" href="{{ path('admin_subscriptions_edit', {id: sub.id}) }}" title="Modifier">
  218. <i class="fa fa-pencil"></i>
  219. </a>
  220. <form method="post" action="{{ path('admin_subscriptions_delete', {id: sub.id}) }}"
  221. onsubmit="return confirm('Supprimer cette inscription ?')" style="display:inline">
  222. <input type="hidden" name="_method" value="DELETE">
  223. <input type="hidden" name="csrf_token" value="{{ csrf_token('subscriptions_deletion' ~ sub.id) }}">
  224. <button class="ra-btn ra-rose" type="submit" title="Supprimer"><i class="fa fa-trash"></i></button>
  225. </form>
  226. {% endif %}
  227. </div>
  228. </td>
  229. </tr>
  230. {% endfor %}
  231. </tbody>
  232. </table>
  233. </div>
  234. {# ── Pagination ───────────────────────────────────────────── #}
  235. {% set pg = subscriptions.currentPageNumber %}
  236. {% set pgTotal = subscriptions.pageCount %}
  237. {% set perPage = subscriptions.itemNumberPerPage %}
  238. {% set from = (pg - 1) * perPage + 1 %}
  239. {% set to = (pg * perPage) < total ? (pg * perPage) : total %}
  240. <div class="pag-bar">
  241. <div class="pag-info">Affichage {{ from }}–{{ to }} sur {{ total }} inscription{{ total > 1 ? 's' : '' }}</div>
  242. <ul class="pag-list">
  243. <li class="{{ pg <= 1 ? 'pg-off' : '' }}">
  244. {% if pg > 1 %}<a href="{{ path('admin_subscriptions', {page: pg - 1}) }}"><i class="fa fa-chevron-left"></i></a>
  245. {% else %}<span><i class="fa fa-chevron-left"></i></span>{% endif %}
  246. </li>
  247. {% for p in 1..pgTotal %}
  248. {% if p == pg %}
  249. <li class="pg-on"><span>{{ p }}</span></li>
  250. {% elseif p == 1 or p == pgTotal or (p >= pg - 2 and p <= pg + 2) %}
  251. <li><a href="{{ path('admin_subscriptions', {page: p}) }}">{{ p }}</a></li>
  252. {% elseif p == pg - 3 or p == pg + 3 %}
  253. <li class="pg-dots"><span>…</span></li>
  254. {% endif %}
  255. {% endfor %}
  256. <li class="{{ pg >= pgTotal ? 'pg-off' : '' }}">
  257. {% if pg < pgTotal %}<a href="{{ path('admin_subscriptions', {page: pg + 1}) }}"><i class="fa fa-chevron-right"></i></a>
  258. {% else %}<span><i class="fa fa-chevron-right"></i></span>{% endif %}
  259. </li>
  260. </ul>
  261. </div>
  262. {% endif %}
  263. </div>
  264. </div>
  265. {% endblock %}