{% extends 'base.html.twig' %} {% block title %}Commerciaux - Admin{% endblock %} {% block body %}
{# ================================================================ EN-TÊTE ================================================================ #}
{% include '_molecules/page-header/page-header.html.twig' with { title: 'Commerciaux', subtitle: 'Vue d\'ensemble du programme d\'apport d\'affaires.', icon: 'wallet', backHref: path('app_dashboard'), backLabel: 'Retour au dashboard' } only %}
{% include '_atoms/button/button.html.twig' with { label: 'Exporter l\'encours', variant: 'primary', size: 'lg', icon: 'download', href: path('admin_commercial_export') } only %} {% include '_atoms/button/button.html.twig' with { label: 'Créer un commercial', variant: 'primary', size: 'lg', icon: 'user-plus', href: path('admin_user_new', {role: 'commercial'}) } only %}
{# ================================================================ QUATRE INDICATEURS — le portefeuille entier ================================================================ #}
{# Le provisoire du mois en cours vit dans le DÉTAIL de cette carte, jamais dans sa valeur : c'est elle qu'on recopie dans l'interface bancaire. #}
{% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Total à virer', value: (totalAVirer|number_format(2, ',', ' ')) ~ ' €', detail: (nbCommerciauxDus > 0 ? (nbCommerciauxDus ~ (nbCommerciauxDus > 1 ? ' commerciaux concernés' : ' commercial concerné')) : 'rien à virer aujourd\'hui') ~ (totalEnCours > 0 ? ' · ' ~ (totalEnCours|number_format(2, ',', ' ')) ~ ' € en cours' : ''), icon: 'wallet', variant: commercial_colors.encours, size: 'sm', layout: 'horizontal' } only %}
{% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Acquis ce mois', value: (series.acquiredThisMonth|number_format(2, ',', ' ')) ~ ' €', detail: 'CA net ' ~ (series.revenueThisMonth|number_format(2, ',', ' ')) ~ ' €', icon: 'chart', variant: commercial_colors.commission, size: 'sm', layout: 'horizontal' } only %} {% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Commerciaux', value: stats.total, detail: stats.active ~ ' actifs · ' ~ stats.suspended ~ ' suspendus', icon: 'users', variant: commercial_colors.commerciaux, size: 'sm', layout: 'horizontal' } only %} {% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Filleuls', value: series.nbFilleuls, detail: series.nbFilleulsAbonnes > 0 ? ('dont ' ~ series.nbFilleulsAbonnes ~ (series.nbFilleulsAbonnes > 1 ? ' abonnés' : ' abonné')) : 'aucun abonné pour l\'instant', icon: 'users', variant: commercial_colors.filleuls, size: 'sm', layout: 'horizontal' } only %}
{# ================================================================ LES GRAPHIQUES Même ordre que le tableau de bord commercial — indicateurs, graphiques, puis tableau : les deux écrans se lisent de la même façon. ================================================================ #}
{% include '_organisms/trend-chart-panel/trend-chart-panel.html.twig' with { title: 'Commissions & chiffre d\'affaires', subtitle: 'Tous commerciaux confondus', 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 %} {# Le même doughnut que le tableau de bord d'un commercial, posé sur le programme entier : de quelles offres sont les filleuls recrutés. Il répartissait l'encours par commercial — une redite du tableau juste en dessous, qui le dit déjà mieux, nommé, chiffré et trié. #} {% include '_organisms/donut-panel/donut-panel.html.twig' with { title: 'Filleuls par offre', subtitle: series.nbFilleuls ~ (series.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', attr: { 'data-filleuls-par-offre': '' } } only %}
{# ================================================================ LE TABLEAU — l'outil de travail Trié par encours dû décroissant côté contrôleur. ================================================================ #}

Commerciaux ({{ pagination.totalItems }})

Trié par encours dû
{% if rows|length > 0 %}
{% for row in rows %} {% set commercial = row.commercial %} {# L'ENCOURS COMPLET, pas le montant d'un seul mois : la colonne s'arrêtait autrefois à la première période due en partant de la plus récente, et l'admin virait ce montant sans avoir vu le total. #} {% endfor %}
Commercial Code parrainage Taux Filleuls Encours dû Statut Action
{% include '_atoms/avatar/avatar.html.twig' with { name: commercial.user.fullName, size: 'sm' } only %}
{{ commercial.user.fullName }} {{ commercial.user.email }}
{{ commercial.referralCode }}
{{ (commercial.commissionRate * 100)|number_format(2, ',', ' ') }} % {{ row.nbFilleuls }} {% if not row.due.isEmpty() %} {{ row.due.total|number_format(2, ',', ' ') }} € {{ row.due.count() }} mois — depuis {{ row.due.oldest().label() }} {% elseif not row.due.hasCurrent() %} {% endif %} {# Le mois en cours, en gris et sans montant gras : il ne se vire pas encore. Il s'affichait nulle part, et l'administrateur attendait le 1er du mois suivant pour savoir ce qui venait. #} {% if row.due.hasCurrent() %} + {{ row.due.currentTotal()|number_format(2, ',', ' ') }} € en cours {% endif %} {% include '_atoms/badge/badge.html.twig' with { preset: commercial.isActive ? 'user-active' : 'user-inactive', size: 'sm' } only %} {% if not row.due.isEmpty() %} {% set aRegler = row.due.oldest() %} {# On propose le mois le PLUS ANCIEN : sans quoi un mois oublié le reste indéfiniment pendant qu'on solde les récents. #}
{% include '_atoms/button/button.html.twig' with { label: 'Payer ' ~ aRegler.label(), variant: 'success', size: 'sm', icon: 'check', type: 'submit' } only %}
{% if row.due.count() > 1 %} et {{ row.due.count() - 1 }} autre{{ row.due.count() > 2 ? 's' : '' }} mois — voir la fiche {% endif %} {% endif %}
{% if pagination.total > 1 %}
{% include '_molecules/pagination/pagination.html.twig' with { currentPage: pagination.current, totalPages: pagination.total, baseUrl: path('admin_commercial_list'), pageParam: pagination.param, size: 'sm' } only %}
{% endif %} {% else %}
{% include '_molecules/empty-state/empty-state.html.twig' with { title: 'Aucun commercial', description: 'Aucun profil commercial n\'a encore été créé.', icon: 'users', variant: 'minimal' } only %}
{% endif %}
{% include 'admin/commercial/_copy-script.html.twig' %} {% endblock %}