feat(backend,frontend): régénération et visualisation inline de la fiche élève PDF

This commit is contained in:
Luc SORIGNET
2026-04-04 17:40:46 +02:00
parent 2d678b732f
commit e37aee2abc
8 changed files with 487 additions and 268 deletions

View File

@ -1,228 +1,319 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Fiche élève de {{ student.last_name }} {{ student.first_name }}</title>
<head>
<meta charset="UTF-8" />
<title>
Fiche élève — {{ student.last_name }} {{ student.first_name }}
</title>
<style>
@page {
size: A4;
margin: 2cm;
}
body {
font-family: 'Arial', sans-serif;
font-size: 12pt;
color: #222;
background: #fff;
margin: 0;
padding: 0;
}
.container {
width: 100%;
padding: 0;
background: #fff;
}
.header {
text-align: center;
margin-bottom: 24px;
border-bottom: 2px solid #4CAF50;
padding-bottom: 12px;
position: relative;
}
.title {
font-size: 22pt;
font-weight: bold;
color: #4CAF50;
margin: 0;
}
.photo {
position: absolute;
top: 0;
right: 0;
width: 90px;
height: 90px;
object-fit: cover;
border: 1px solid #4CAF50;
border-radius: 8px;
}
.section {
margin-bottom: 32px; /* Espacement augmenté entre les sections */
}
.section-title {
font-size: 15pt;
font-weight: bold;
color: #4CAF50;
margin-bottom: 18px; /* Espacement sous le titre de section */
border-bottom: 1px solid #4CAF50;
padding-bottom: 2px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 8px;
}
th, td {
border: 1px solid #bbb;
padding: 6px 8px;
text-align: left;
}
th {
background: #f3f3f3;
font-weight: bold;
}
tr:nth-child(even) {
background: #fafafa;
}
.label-cell {
font-weight: bold;
width: 30%;
background: #f3f3f3;
}
.value-cell {
width: 70%;
}
.signature {
margin-top: 30px;
text-align: right;
font-style: italic;
color: #555;
}
.signature-text {
font-weight: bold;
color: #333;
}
.subsection-title {
font-size: 12pt;
color: #333;
margin: 8px 0 4px 0;
font-weight: bold;
}
@page {
size: A4;
margin: 1.5cm 2cm;
}
body {
font-family: "Helvetica", "Arial", sans-serif;
font-size: 10pt;
color: #1e293b;
background: #fff;
margin: 0;
padding: 0;
line-height: 1.4;
}
/* ── Header ── */
.header-table {
width: 100%;
border: none;
margin-bottom: 16px;
}
.header-table td {
border: none;
padding: 0;
vertical-align: middle;
}
.header-left {
width: 80%;
}
.header-right {
width: 20%;
text-align: right;
}
.school-name {
font-size: 10pt;
color: #64748b;
margin: 0 0 4px 0;
letter-spacing: 0.5px;
}
.title {
font-size: 20pt;
font-weight: bold;
color: #064e3b;
margin: 0 0 2px 0;
}
.subtitle {
font-size: 11pt;
color: #059669;
margin: 0;
font-weight: normal;
}
.header-line {
border: none;
border-top: 3px solid #059669;
margin: 12px 0 20px 0;
}
.photo {
width: 80px;
height: 80px;
object-fit: cover;
border: 2px solid #059669;
border-radius: 4px;
}
/* ── Sections ── */
.section {
margin-bottom: 20px;
}
.section-header {
background-color: #059669;
color: #ffffff;
font-size: 11pt;
font-weight: bold;
padding: 6px 12px;
margin-bottom: 0;
letter-spacing: 0.5px;
border-radius: 2px 2px 0 0;
}
.subsection-title {
font-size: 10pt;
color: #064e3b;
font-weight: bold;
padding: 6px 0 2px 0;
margin: 8px 0 4px 0;
border-bottom: 1px solid #d1d5db;
}
/* ── Tables ── */
table.data {
width: 100%;
border-collapse: collapse;
margin-bottom: 4px;
}
table.data td {
padding: 5px 8px;
border: 1px solid #e2e8f0;
font-size: 10pt;
vertical-align: top;
}
table.data .label {
font-weight: bold;
color: #064e3b;
background-color: #f0fdf4;
width: 25%;
white-space: nowrap;
}
table.data .value {
color: #1e293b;
width: 25%;
}
/* ── Paiement ── */
table.payment {
width: 100%;
border-collapse: collapse;
}
table.payment td {
padding: 5px 8px;
border: 1px solid #e2e8f0;
font-size: 10pt;
}
table.payment .label {
font-weight: bold;
color: #064e3b;
background-color: #f0fdf4;
width: 35%;
}
table.payment .value {
width: 65%;
}
/* ── Footer / Signature ── */
.signature-block {
margin-top: 24px;
padding: 10px 12px;
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 2px;
}
.signature-block p {
margin: 0;
font-size: 10pt;
color: #475569;
}
.signature-date {
font-weight: bold;
color: #064e3b;
}
.footer-line {
border: none;
border-top: 2px solid #059669;
margin: 20px 0 8px 0;
}
.footer-text {
text-align: center;
font-size: 8pt;
color: #94a3b8;
}
</style>
</head>
<body>
</head>
<body>
{% load myTemplateTag %}
<div class="container">
<!-- Header Section -->
<div class="header">
<h1 class="title">Fiche élève de {{ student.last_name }} {{ student.first_name }}</h1>
{% if student.photo %}
<img src="{{ student.get_photo_url }}" alt="Photo de l'élève" class="photo" />
{% else %}
<img src="/static/img/default-photo.jpg" alt="Photo par défaut" class="photo" />
{% endif %}
</div>
<!-- Élève -->
<div class="section">
<div class="section-title">ÉLÈVE</div>
<table>
<tr>
<td class="label-cell">Nom</td>
<td class="value-cell">{{ student.last_name }}</td>
<td class="label-cell">Prénom</td>
<td class="value-cell">{{ student.first_name }}</td>
</tr>
<tr>
<td class="label-cell">Adresse</td>
<td class="value-cell" colspan="3">{{ student.address }}</td>
</tr>
<tr>
<td class="label-cell">Genre</td>
<td class="value-cell">{{ student|getStudentGender }}</td>
<td class="label-cell">Né(e) le</td>
<td class="value-cell">{{ student.birth_date }}</td>
</tr>
<tr>
<td class="label-cell">À</td>
<td class="value-cell">{{ student.birth_place }} ({{ student.birth_postal_code }})</td>
<td class="label-cell">Nationalité</td>
<td class="value-cell">{{ student.nationality }}</td>
</tr>
<tr>
<td class="label-cell">Niveau</td>
<td class="value-cell">{{ student|getStudentLevel }}</td>
<td class="label-cell"></td>
<td class="value-cell"></td>
</tr>
</table>
</div>
<!-- ═══════ HEADER ═══════ -->
<table class="header-table">
<tr>
<td class="header-left">
{% if establishment %}
<p class="school-name">{{ establishment.name }}</p>
{% endif %}
<h1 class="title">Fiche &Eacute;l&egrave;ves</h1>
<!-- prettier-ignore -->
<p class="subtitle">{{ student.last_name }} {{ student.first_name }}{% if school_year %} — {{ school_year }}{% endif %}</p>
</td>
<td class="header-right">
{% if student.photo %}
<img src="{{ student.get_photo_url }}" alt="Photo" class="photo" />
{% endif %}
</td>
</tr>
</table>
<hr class="header-line" />
<!-- Responsables -->
<div class="section">
<div class="section-title">RESPONSABLES</div>
{% for guardian in student.getGuardians %}
<div>
<div class="subsection-title">Responsable {{ forloop.counter }}</div>
<table>
<tr>
<td class="label-cell">Nom</td>
<td class="value-cell">{{ guardian.last_name }}</td>
<td class="label-cell">Prénom</td>
<td class="value-cell">{{ guardian.first_name }}</td>
</tr>
<tr>
<td class="label-cell">Adresse</td>
<td class="value-cell" colspan="3">{{ guardian.address }}</td>
</tr>
<tr>
<td class="label-cell">Email</td>
<td class="value-cell" colspan="3">{{ guardian.email }}</td>
</tr>
<tr>
<td class="label-cell">Né(e) le</td>
<td class="value-cell">{{ guardian.birth_date }}</td>
<td class="label-cell">Téléphone</td>
<td class="value-cell">{{ guardian.phone|phone_format }}</td>
</tr>
<tr>
<td class="label-cell">Profession</td>
<td class="value-cell" colspan="3">{{ guardian.profession }}</td>
</tr>
</table>
</div>
{% endfor %}
</div>
<!-- Fratrie -->
<div class="section">
<div class="section-title">FRATRIE</div>
{% for sibling in student.getSiblings %}
<div>
<div class="subsection-title">Frère/Soeur {{ forloop.counter }}</div>
<table>
<tr>
<td class="label-cell">Nom</td>
<td class="value-cell">{{ sibling.last_name }}</td>
<td class="label-cell">Prénom</td>
<td class="value-cell">{{ sibling.first_name }}</td>
</tr>
<tr>
<td class="label-cell">Né(e) le</td>
<td class="value-cell" colspan="3">{{ sibling.birth_date }}</td>
</tr>
</table>
</div>
{% endfor %}
</div>
<!-- Paiement -->
<div class="section">
<div class="section-title">MODALITÉS DE PAIEMENT</div>
<table>
<tr>
<td class="label-cell">Frais d'inscription</td>
<td class="value-cell">{{ student|getRegistrationPaymentMethod }} en {{ student|getRegistrationPaymentPlan }}</td>
</tr>
<tr>
<td class="label-cell">Frais de scolarité</td>
<td class="value-cell">{{ student|getTuitionPaymentMethod }} en {{ student|getTuitionPaymentPlan }}</td>
</tr>
</table>
</div>
<!-- Signature -->
<div class="signature">
Fait le <span class="signature-text">{{ signatureDate }}</span> à <span class="signature-text">{{ signatureTime }}</span>
</div>
<!-- ═══════ ÉLÈVE ═══════ -->
<div class="section">
<div class="section-header">INFORMATIONS DE L'ÉLÈVE</div>
<table class="data">
<tr>
<td class="label">Nom</td>
<td class="value">{{ student.last_name }}</td>
<td class="label">Prénom</td>
<td class="value">{{ student.first_name }}</td>
</tr>
<tr>
<td class="label">Genre</td>
<td class="value">{{ student|getStudentGender }}</td>
<td class="label">Niveau</td>
<td class="value">{{ student|getStudentLevel }}</td>
</tr>
<tr>
<td class="label">Date de naissance</td>
<td class="value">{{ student.formatted_birth_date }}</td>
<td class="label">Lieu de naissance</td>
<!-- prettier-ignore -->
<td class="value">{{ student.birth_place }}{% if student.birth_postal_code %} ({{ student.birth_postal_code }}){% endif %}</td>
</tr>
<tr>
<td class="label">Nationalité</td>
<td class="value">{{ student.nationality }}</td>
<td class="label">Médecin traitant</td>
<td class="value">{{ student.attending_physician }}</td>
</tr>
<tr>
<td class="label">Adresse</td>
<td class="value" colspan="3">{{ student.address }}</td>
</tr>
</table>
</div>
</body>
</html>
<!-- ═══════ RESPONSABLES ═══════ -->
<div class="section">
<div class="section-header">RESPONSABLES LÉGAUX</div>
{% for guardian in student.getGuardians %}
<div class="subsection-title">Responsable {{ forloop.counter }}</div>
<table class="data">
<tr>
<td class="label">Nom</td>
<td class="value">{{ guardian.last_name }}</td>
<td class="label">Prénom</td>
<td class="value">{{ guardian.first_name }}</td>
</tr>
<tr>
<td class="label">Date de naissance</td>
<td class="value">{{ guardian.birth_date }}</td>
<td class="label">Téléphone</td>
<td class="value">{{ guardian.phone|phone_format }}</td>
</tr>
<tr>
<td class="label">Email</td>
<td class="value" colspan="3">{{ guardian.email }}</td>
</tr>
<tr>
<td class="label">Adresse</td>
<td class="value" colspan="3">{{ guardian.address }}</td>
</tr>
<tr>
<td class="label">Profession</td>
<td class="value" colspan="3">{{ guardian.profession }}</td>
</tr>
</table>
{% empty %}
<p style="color: #94a3b8; font-style: italic; padding: 8px">
Aucun responsable renseigné.
</p>
{% endfor %}
</div>
<!-- ═══════ FRATRIE ═══════ -->
{% if student.getSiblings %}
<div class="section">
<div class="section-header">FRATRIE</div>
{% for sibling in student.getSiblings %}
<div class="subsection-title">Frère / Sœur {{ forloop.counter }}</div>
<table class="data">
<tr>
<td class="label">Nom</td>
<td class="value">{{ sibling.last_name }}</td>
<td class="label">Prénom</td>
<td class="value">{{ sibling.first_name }}</td>
</tr>
<tr>
<td class="label">Date de naissance</td>
<td class="value" colspan="3">{{ sibling.birth_date }}</td>
</tr>
</table>
{% endfor %}
</div>
{% endif %}
<!-- ═══════ PAIEMENT ═══════ -->
<div class="section">
<div class="section-header">MODALITÉS DE PAIEMENT</div>
<table class="payment">
<tr>
<td class="label">Frais d'inscription</td>
<!-- prettier-ignore -->
<td class="value">{{ student|getRegistrationPaymentMethod }} — {{ student|getRegistrationPaymentPlan }}</td>
</tr>
<tr>
<td class="label">Frais de scolarité</td>
<!-- prettier-ignore -->
<td class="value">{{ student|getTuitionPaymentMethod }} — {{ student|getTuitionPaymentPlan }}</td>
</tr>
</table>
</div>
<!-- ═══════ SIGNATURE ═══════ -->
<div class="signature-block">
<p>
Document généré le
<span class="signature-date">{{ signatureDate }}</span> à
<span class="signature-date">{{ signatureTime }}</span>
</p>
</div>
<hr class="footer-line" />
<p class="footer-text">
Ce document est généré automatiquement et fait office de fiche
d'inscription.
</p>
</body>
</html>