diff --git a/backend/app/routers/reunions.py b/backend/app/routers/reunions.py index 636dbb1..ab50705 100644 --- a/backend/app/routers/reunions.py +++ b/backend/app/routers/reunions.py @@ -101,66 +101,91 @@ async def _html_rapport_rencontre(reunion: Reunion, membre, db: AsyncSession) -> ) annonces = reunion.annonces or [] - annonces_html = "".join(f"
  • {escape(a)}
  • " for a in annonces) or "
  • Aucune annonce.
  • " + annonces_texte = "\n".join(annonces) + collecte_texte = "Aucune collecte saisie." + if collecte: + detenteur = await db.get(Membre, collecte.detenteur_id) if collecte.detenteur_id else None + collecte_texte = ( + f"{float(collecte.montant):.2f} $" + f"\nDétenteur: {detenteur.prenom if detenteur else '—'}" + f"\nStatut: {collecte.statut}" + ) + ventes_lit_texte = "\n".join( + row.replace("", "") + .replace("", " · ") + .replace("", " · ") + .replace("", "") + for row in ventes_lit_rows + ) + jetons_texte = "\n".join( + row.replace("", "") + .replace("", " · ") + .replace("", "") + for row in jetons_rows + ) + ventes_jetons_texte = "\n".join( + row.replace("", "") + .replace("", " · ") + .replace("", " · ") + .replace("", "") + for row in ventes_jetons_rows + ) + notes_texte = "\n\n".join([texte for texte in [reunion.note, annonces_texte] if texte]) + + def bloc(label: str, contenu: str = "", cls: str = "") -> str: + return ( + f"
    " + f"{escape(contenu or '')}
    {escape(label)}
    " + ) + css = """ - body { font-family: 'DejaVu Sans', sans-serif; font-size: 10.5pt; color: #27323a; margin: 1.6cm; } - .header { border-bottom: 3px solid #1a5276; padding-bottom: 12pt; margin-bottom: 18pt; } - .groupe { color: #1a5276; font-size: 20pt; font-weight: 800; } - .coords { color: #667; font-size: 9pt; margin-top: 3pt; } - h1 { font-size: 16pt; color: #1a5276; margin: 0 0 12pt; } - h2 { font-size: 12pt; color: #1a5276; margin: 16pt 0 6pt; border-bottom: 1px solid #d8e2ea; padding-bottom: 3pt; } - .meta { display: grid; grid-template-columns: 1fr 1fr; gap: 8pt; } - .box { border: 1px solid #d8e2ea; border-radius: 6pt; padding: 8pt; background: #f8fafc; } - .label { font-size: 8pt; color: #667; text-transform: uppercase; font-weight: 700; } - .value { font-size: 11pt; font-weight: 700; margin-top: 2pt; } - table { width: 100%; border-collapse: collapse; margin-top: 5pt; } - th { text-align: left; background: #1a5276; color: white; padding: 5pt; font-size: 8.5pt; } - td { border-bottom: 1px solid #e5e9ef; padding: 5pt; vertical-align: top; } - .montant { text-align: right; white-space: nowrap; } - .note { white-space: pre-wrap; } - .page-break { break-before: page; page-break-before: always; } - .footer { margin-top: 20pt; color: #889; font-size: 8pt; text-align: center; } + @page { size: letter; margin: 0.55in 0.65in; } + body { font-family: Arial, 'DejaVu Sans', sans-serif; color: #111; font-size: 10pt; } + .header { text-align: center; margin: 12pt 0 40pt; } + .groupe { font-size: 21pt; font-weight: 400; margin-bottom: 16pt; } + .district { font-size: 16pt; margin-bottom: 18pt; } + h1 { font-size: 14pt; font-weight: 500; letter-spacing: 0; margin: 0; } + .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18pt 18pt; } + .field { break-inside: avoid; } + .field.full { grid-column: 1 / -1; } + .box { + border: 1pt solid #222; + min-height: 27pt; + padding: 6pt 8pt; + white-space: pre-wrap; + line-height: 1.25; + overflow: hidden; + } + .label { font-style: italic; font-size: 8.5pt; margin: 4pt 0 0 1pt; } + .short .box { min-height: 24pt; } + .theme .box { min-height: 58pt; } + .medium .box { min-height: 58pt; } + .deep .box { min-height: 95pt; } + .notes .box { min-height: 150pt; } + .footer { margin-top: 10pt; color: #777; font-size: 7.5pt; text-align: center; } """ groupe_nom = groupe.nom if groupe and groupe.nom else "Groupe" - coords = " · ".join([ - item for item in [ - groupe.adresse_local if groupe else None, - f"District {groupe.district}" if groupe and groupe.district else None, - ] if item - ]) - ventes_lit_html = ( - "" - + "".join(ventes_lit_rows) + "
    TitreQtéMontantVendu parStatut
    " - if ventes_lit_rows else "

    Aucune vente de littérature.

    " - ) - ventes_jetons_html = ( - "" - + "".join(ventes_jetons_rows) + "
    TypeQtéMontantVendu parStatut
    " - if ventes_jetons_rows else "

    Aucune vente de jetons.

    " - ) - jetons_html = ( - "" + "".join(jetons_rows) + "
    TypeMembreDétail
    " - if jetons_rows else "

    Aucun jeton/gâteau comptabilisé.

    " - ) + district = f"District {groupe.district}" if groupe and groupe.district else "District" html = f"""
    {escape(groupe_nom)}
    -
    {escape(coords)}
    +
    {escape(district)}
    +

    RÉUNION HEBDOMADAIRE

    -

    Rapport de rencontre

    -
    -
    Date
    {reunion.date_reunion.date().isoformat()}
    -
    Heure
    {reunion.date_reunion.strftime('%H:%M')}
    -
    Sujet
    {escape(reunion.theme or 'Rencontre hebdomadaire')}
    -
    Animé par
    {escape(animateur.prenom if animateur else '—')}
    +
    + {bloc("DATE ( AAAA / MM / JJ)", reunion.date_reunion.date().isoformat(), "short")} + {bloc("PRÉSENCES", "", "short")} + {bloc("SALLE & CAFÉ", groupe.adresse_local if groupe else "", "short")} + {bloc("ANIMATION", animateur.prenom if animateur else "", "short")} + {bloc("THÈME DE LA DISCUSSION", reunion.theme or "", "full theme")} + {bloc("NOUVEAU(X)", "", "medium")} + {bloc("ANNIVERSAIRES / JETONS", jetons_texte, "medium")} + {bloc("COLLECTE", collecte_texte, "short")} + {bloc("DÉPENSES", "", "deep")} + {bloc("VENTES (Littérature)", ventes_lit_texte, "short")} + {bloc("VENTES (Jetons)", ventes_jetons_texte, "short")} + {bloc("NOTES", notes_texte, "full notes")}
    -

    Annonces

    -

    Notes

    {escape(reunion.note or 'Aucune note.')}
    -

    Collecte

    {collecte_html} -
    -

    Ventes de littérature

    {ventes_lit_html} -

    Ventes de jetons

    {ventes_jetons_html} -

    Jetons/Gâteaux

    {jetons_html} """ return html, jour diff --git a/data/import/Modèle de Saint-Basile.pdf b/data/import/Modèle de Saint-Basile.pdf new file mode 100644 index 0000000..bfc3601 Binary files /dev/null and b/data/import/Modèle de Saint-Basile.pdf differ diff --git a/frontend/src/pages/ModePapier.jsx b/frontend/src/pages/ModePapier.jsx index 9f14c33..d24c9cf 100644 --- a/frontend/src/pages/ModePapier.jsx +++ b/frontend/src/pages/ModePapier.jsx @@ -20,36 +20,48 @@ const formulaires = [ }, ]; -const lignes = (n) => Array.from({ length: n }, (_, i) => `
    ${i + 1}.
    `).join(''); - function htmlFormulaire(type, groupe) { const nomGroupe = groupe?.nom || 'Groupe'; - const commun = ` + const district = groupe?.district ? `District ${groupe.district}` : 'District'; + const lieu = groupe?.adresse_local || ''; + const bloc = (label, contenu = '', cls = '') => ` +
    +
    ${contenu}
    +
    ${label}
    +
    `; + const lignes = (n) => Array.from({ length: n }, (_, i) => `
    ${i + 1}.
    `).join(''); + const entete = (titre) => `

    ${nomGroupe}

    -

    Formulaire papier de reprise en main

    -
    -
    -
    Date: ____________________
    -
    Responsable: ____________________
    +

    ${district}

    +

    ${titre}

    `; const sections = { rencontre: ` -

    Rencontre hebdomadaire

    -
    -
    Montant: __________
    Détenteur: __________
    - ${lignes(6)} - ${lignes(6)} -
    `, + ${entete('RÉUNION HEBDOMADAIRE')} +
    + ${bloc('DATE ( AAAA / MM / JJ)', '', 'short')} + ${bloc('PRÉSENCES', '', 'short')} + ${bloc('SALLE & CAFÉ', lieu, 'short')} + ${bloc('ANIMATION', '', 'short')} + ${bloc('THÈME DE LA DISCUSSION', '', 'full theme')} + ${bloc('NOUVEAU(X)', '', 'medium')} + ${bloc('ANNIVERSAIRES / JETONS', '', 'medium')} + ${bloc('COLLECTE', '', 'short')} + ${bloc('DÉPENSES', '', 'deep')} + ${bloc('VENTES (Littérature)', '', 'short')} + ${bloc('VENTES (Jetons)', '', 'short')} + ${bloc('NOTES', '', 'full notes')} +
    `, assemblee: ` -

    Assemblée d'affaires

    + ${entete("ASSEMBLÉE D'AFFAIRES")} ${lignes(12)}
    ${lignes(10)} ${lignes(6)}
    `, tresorerie: ` -

    Trésorerie

    + ${entete('TRÉSORERIE')} ${lignes(8)} ${lignes(8)} ${lignes(8)} @@ -57,13 +69,14 @@ function htmlFormulaire(type, groupe) { }; return `${nomGroupe} - ${type} ${commun}${sections[type]}`; + ${sections[type]}`; } export default function ModePapier() {