{% extends 'base.html.twig' %} {% block title %}Espace commercial — Bubul{% endblock %} {% block body %}
{# ================================================================ EN-TÊTE — titre, actions, code de parrainage ================================================================ #}
{% include '_molecules/page-header/page-header.html.twig' with { title: 'Espace commercial', subtitle: 'Filleuls, chiffre d\'affaires et commission — ' ~ periodLabelLong ~ '.', icon: 'chart', backHref: path('app_dashboard'), backLabel: 'Retour au dashboard' } only %}
{# Renfort en tokens : l'état vient du serveur (tokenRequestSentToday), jamais d'un état masqué côté client — la demande elle-même est bridée à une par jour dans TokenRequestController. #} {% if tokenRequestSentToday %} {% include '_atoms/icon/icon.html.twig' with { name: 'token', size: 'xs' } only %} Demande effectuée {% else %}
{% include '_atoms/button/button.html.twig' with { label: 'Demander des tokens', variant: 'soft-primary', size: 'md', icon: 'token', type: 'submit' } only %}
{% endif %} {# Le code reste ici, mais réduit à sa donnée : le code lui-même, cliquable pour être copié. Le lien d'invitation, le QR et son téléchargement vivent sur /settings/parrainage — les dupliquer ici obligeait à maintenir deux endroits pour la même chose. #}
Code de parrainage
Lien et QR →
{# ================================================================ QUATRE INDICATEURS — poids égal Les montants viennent du MÊME résumé que Transactions et Parrainage : mêmes mots, mêmes nombres, partout. Aucune somme n'est faite ici. ================================================================ #}
{% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Filleuls', value: nbFilleuls, icon: 'users', detail: nbFilleulsAbonnes > 0 ? ('dont ' ~ nbFilleulsAbonnes ~ (nbFilleulsAbonnes > 1 ? ' abonnés' : ' abonné')) : 'aucun abonné pour l\'instant', variant: commercial_colors.filleuls, size: 'sm', layout: 'horizontal', href: path('app_commercial_transactions') } only %} {# Le taux et l'assiette se lisent ICI, là où ils s'appliquent. Le bandeau bleu de sept lignes qui les portait au-dessus de la page a disparu. #} {% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Ce mois-ci', value: (summary.acquiredThisMonth|number_format(2, ',', ' ')) ~ ' €', icon: 'chart', variant: commercial_colors.commission, size: 'sm', layout: 'horizontal', detail: 'CA net ' ~ (caDuMois|number_format(2, ',', ' ')) ~ ' € · taux ' ~ ((tauxCourant * 100)|number_format(2, ',', ' ')) ~ ' %', href: path('app_commercial_transactions') } only %} {% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Reste à recevoir', value: (summary.dueNow|number_format(2, ',', ' ')) ~ ' €', icon: 'wallet', variant: commercial_colors.encours, size: 'sm', layout: 'horizontal', detail: summary.carryOverIn < 0 ? ((summary.carryOverIn|number_format(2, ',', ' ')) ~ ' € reportés des mois précédents') : (summary.duePeriodCount > 0 ? (summary.duePeriodCount ~ ' mois clos en attente') : 'aucun mois clos en attente'), href: path('app_commercial_transactions') } only %} {% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Déjà versé', value: (summary.alreadyPaid|number_format(2, ',', ' ')) ~ ' €', icon: 'check-circle', variant: commercial_colors.verse, size: 'sm', layout: 'horizontal', detail: 'total des virements reçus', href: path('app_commercial_paiements') } only %}
{# ================================================================ DEUX GRAPHIQUES Commissions et chiffre d'affaires ne font qu'un graphique à deux courbes : c'est la même histoire, lue à deux échelles. Les douze points sont rendus une fois ; le sélecteur les découpe côté client, sans requête. ================================================================ #}
{% include '_organisms/trend-chart-panel/trend-chart-panel.html.twig' with { title: 'Commissions & chiffre d\'affaires', subtitle: 'Commission acquise (trait plein) et CA net généré (pointillés)', labels: series.labels, primary: series.commissions, secondary: series.revenue, primaryLabel: 'Commission acquise', secondaryLabel: 'CA net généré', suffix: ' €', icon: 'chart', iconColor: 'primary', height: 'md', emptyText: 'Aucune commission sur la période' } only %} {# Mêmes couleurs que le doughnut de l'écran admin : c'est le même graphique, lu sur ses propres filleuls plutôt que sur tout le programme. #} {% include '_organisms/donut-panel/donut-panel.html.twig' with { title: 'Filleuls par offre', subtitle: nbFilleuls ~ (nbFilleuls > 1 ? ' filleuls recrutés' : ' filleul recruté'), labels: series.planLabels, data: series.planCounts, icon: 'users-group', iconColor: commercial_colors.filleuls, tokens: commercial_plan_tokens, emptyIcon: 'users', emptyText: 'Aucun filleul pour l\'instant' } only %}
{# ================================================================ DEUX TABLEAUX CÔTE À CÔTE ================================================================ #}
{# ── Vos derniers mois ── #}

Vos derniers mois

Voir le détail →
{# Lu depuis le RELEVÉ (CommissionStatement), jamais depuis les CommercialPayout bruts : un relevé resté « pending » n'est le constat d'aucun virement. #} {% if lastPeriods is empty %}
Aucune transaction pour l'instant. Dès qu'un filleul souscrit une offre payante, son paiement vous rapporte une commission et apparaît ici.
{% else %}
{% for p in lastPeriods %} {% endfor %}
Mois Commission acquise Statut
{{ p.label() }} {{ p.commissionAmount|number_format(2, ',', ' ') }} € {% if p.hasLateLines() %} + {{ p.lateCommissionAmount|number_format(2, ',', ' ') }} € hors relevé {% endif %} {% include '_atoms/badge/badge.html.twig' with { label: p.status == 'paye' ? 'Versé' : (p.status == 'en_cours' ? 'Mois en cours' : 'À verser'), variant: p.status == 'paye' ? 'success' : (p.status == 'en_cours' ? 'info' : 'warning'), size: 'sm' } only %}
{% endif %}
{# ── Vos filleuls ── Une seule liste : les filleuls, abonnés ou non, ET les invitations restées sans suite. Ces deux dernières populations étaient en pied de page de Transactions, où personne ne les cherchait. #}
{# Le compteur suit les LIGNES listées, invitations comprises, et le titre le dit. Le KPI « Filleuls » du haut ne compte que les comptes réels : deux nombres différents sous deux noms différents, plutôt que le même mot portant deux valeurs. #}

Vos filleuls et invitations ({{ filleuls|length }})

Inviter →
{% if filleuls is empty %}
Aucun filleul pour l'instant. Partagez votre code pour commencer.
{% else %}
{% for f in filleuls %} {% endfor %}
Filleul Offre État Depuis
{{ f.nom }} {% if f.type == 'filleul' %} {{ f.email }} {% endif %} {% if f.type == 'invitation' %} {% else %} {% include '_atoms/plan-badge/plan-badge.html.twig' with { name: f.planName, gradient: f.planGradient, isFree: f.planIsFree, isAnnual: f.planIsAnnual } only %} {% endif %} {% if f.etat == 'abonne' %} {% include '_atoms/badge/badge.html.twig' with { label: 'Abonné', variant: 'success', size: 'sm' } only %} {% elseif f.etat == 'sans_paiement' %} {% include '_atoms/badge/badge.html.twig' with { label: 'Sans paiement', variant: 'neutral', size: 'sm' } only %} {% else %}
{% include '_atoms/badge/badge.html.twig' with { label: 'Invitation en attente', variant: 'warning', size: 'sm' } only %}
{% include '_atoms/button/button.html.twig' with { label: 'Relancer', variant: 'soft-primary', size: 'xs', icon: 'refresh', type: 'submit', attr: { 'aria-label': "Renvoyer l'invitation à " ~ f.email } } only %}
{% endif %}
{{ f.depuis ?? '—' }}
{% endif %}
{% include 'admin/commercial/_copy-script.html.twig' %}
{% endblock %}