Implement statistics and layout enhancements for promoteur hub

This commit introduces a new function, `fxPromoteurHubGetEpreuveStats`, to aggregate and return statistics for events, including sold tickets, check-ins, and waiting list details. Additionally, it adds extensive CSS styles for the promoteur hub, enhancing the layout and visual presentation of statistics, alerts, and event tiles. These changes improve the user experience by providing clearer information and a more organized interface for event management.
This commit is contained in:
2026-06-27 17:03:26 -04:00
parent 67d7675e11
commit 3e8112ec0b
3 changed files with 661 additions and 1 deletions

View File

@ -3183,3 +3183,214 @@ a.ms1-trad-link.btn-aide-trad{
border-top: 1px solid #e9ecef;
padding-top: 1rem;
}
/* ===== Hub promoteur : stats par epreuve (panneau de droite) ===== */
.promoteur-hub-card-row {
align-items: stretch;
}
@media (min-width: 992px) {
.promoteur-hub-card-stats {
border-left: 1px dashed #e3c4a6;
}
}
.promoteur-hub-card-stats {
margin-top: 0.5rem;
}
@media (min-width: 992px) {
.promoteur-hub-card-stats {
margin-top: 0;
}
}
/* --- Bande flash --- */
.promoteur-hub-flash {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
background: linear-gradient(135deg, #fff7f0 0%, #fdeede 100%);
border: 1px solid #f3d6b8;
border-radius: 12px;
padding: 0.6rem 0.75rem;
margin-bottom: 0.6rem;
}
.phub-flash-item {
flex: 1 1 auto;
min-width: 72px;
text-align: center;
line-height: 1.1;
}
.phub-flash-num {
display: block;
font-size: 1.5rem;
font-weight: 700;
color: #1f3b63;
}
.phub-flash-lbl {
display: block;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.03em;
color: #8a6d52;
}
.phub-flash-main .phub-flash-num {
color: #e8821e;
font-size: 1.85rem;
}
/* --- Badges d'alerte --- */
.promoteur-hub-badges {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
margin-bottom: 0.6rem;
}
.phub-badge {
font-size: 0.72rem;
font-weight: 600;
padding: 0.2rem 0.55rem;
border-radius: 999px;
white-space: nowrap;
}
.phub-badge-danger { background: #fde2e1; color: #b3261e; }
.phub-badge-warn { background: #fdecc8; color: #9a6700; }
.phub-badge-info { background: #dceafd; color: #1f5fb3; }
.phub-badge-soft { background: #eceff3; color: #4a5568; }
/* --- Grille de tuiles par epreuve --- */
.promoteur-hub-epreuves-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.6rem;
}
.phub-epr-tile {
background: #fff;
border: 1px solid #e7ebf0;
border-left: 4px solid #adb5bd;
border-radius: 10px;
padding: 0.6rem 0.7rem;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
}
.phub-epr-head {
margin-bottom: 0.35rem;
}
.phub-epr-titre {
display: block;
font-weight: 700;
color: #1f3b63;
font-size: 0.9rem;
line-height: 1.15;
}
.phub-epr-cat {
display: inline-block;
font-size: 0.68rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.02em;
color: #8a6d52;
}
.phub-epr-sub {
display: block;
font-size: 0.72rem;
color: #6b7785;
}
.phub-epr-big {
line-height: 1;
margin: 0.15rem 0 0.4rem;
}
.phub-epr-bignum {
font-size: 2.3rem;
font-weight: 800;
color: #1f3b63;
}
.phub-epr-max {
font-size: 1rem;
font-weight: 600;
color: #9aa6b2;
margin-left: 0.15rem;
}
.phub-epr-biglbl {
display: block;
font-size: 0.66rem;
text-transform: uppercase;
letter-spacing: 0.03em;
color: #8a93a0;
margin-top: 0.1rem;
}
.phub-epr-bar {
height: 8px;
background: #eef1f4;
border-radius: 999px;
overflow: hidden;
}
.phub-epr-bar > span {
display: block;
height: 100%;
border-radius: 999px;
background: #2f9e44;
transition: width 0.4s ease;
}
.phub-epr-barlbl {
font-size: 0.7rem;
color: #6b7785;
margin-top: 0.2rem;
}
.phub-epr-illimite {
color: #6b7785;
font-style: italic;
}
.phub-epr-chips {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
margin-top: 0.45rem;
}
.phub-chip {
font-size: 0.68rem;
font-weight: 600;
padding: 0.15rem 0.45rem;
border-radius: 6px;
background: #f1f4f7;
color: #45525f;
white-space: nowrap;
}
.phub-chip-prix { background: #e7f3ea; color: #1d6b32; }
.phub-chip-next { background: #fdecc8; color: #8a5a00; }
.phub-chip-checkin { background: #e2eefb; color: #1f5fb3; }
.phub-chip-attente { background: #efe6fb; color: #6b3fb3; }
/* --- Etats de remplissage (bordure + barre) --- */
.phub-etat-ok { border-left-color: #2f9e44; }
.phub-etat-attention { border-left-color: #f59f00; }
.phub-etat-attention .phub-epr-bar > span { background: #f59f00; }
.phub-etat-presque { border-left-color: #e8590c; }
.phub-etat-presque .phub-epr-bar > span { background: #e8590c; }
.phub-etat-complete { border-left-color: #b3261e; }
.phub-etat-complete .phub-epr-bar > span { background: #b3261e; }
.phub-etat-illimitee { border-left-color: #868e96; }

View File

@ -487,12 +487,453 @@ function fxPromoteurHubRenderEventPanel($arrItem, $strLangue)
return $html;
}
/**
* Statistiques par epreuve pour un evenement (vendus / restant / max,
* check-in, liste d'attente, prix courant + date du prochain palier).
* Requetes agregees (1 par metrique) plutot qu'une boucle fxGetQuantites,
* pour rester rapide dans le hub.
*
* Retourne array('epreuves' => [...], 'flash' => [...]) ou null si aucune epreuve.
*/
function fxPromoteurHubGetEpreuveStats($intEveId, $strLangue = 'fr')
{
global $objDatabase;
$intEveId = intval($intEveId);
if ($intEveId <= 0 || !isset($objDatabase)) {
return null;
}
$strLang = in_array($strLangue, array('fr', 'en'), true) ? $strLangue : 'fr';
$strAlt = ($strLang === 'fr') ? 'en' : 'fr';
// ---- Liste des epreuves ----
$sqlEpreuves = "
SELECT epr_id, epr_categorie_fr, epr_categorie_en, epr_type_fr, epr_type_en,
epr_nom_fr, epr_nom_en, epr_date, epr_heure, epr_date_limite,
epr_qte, epr_qte_origine, epr_qte_limitee, epr_equipe, epr_nb_participants
FROM inscriptions_epreuves
WHERE eve_id = " . $intEveId . " AND epr_actif = 1
ORDER BY epr_tri, epr_type_" . $strLang . ", epr_nom_" . $strLang . "
";
$tabEpreuves = $objDatabase->fxGetResults($sqlEpreuves);
if (!is_array($tabEpreuves) || count($tabEpreuves) === 0) {
return null;
}
$arrIds = array();
for ($i = 1; $i <= count($tabEpreuves); $i++) {
$arrIds[] = intval($tabEpreuves[$i]['epr_id']);
}
$strIds = implode(',', $arrIds);
// ---- Vendus / inscrits (confirmes, non annules) ----
$arrVendus = array();
$sqlVendus = "
SELECT p.epr_id AS epr_id, COUNT(DISTINCT e.pec_id_original) AS nb
FROM resultats_epreuves_commandees e
JOIN resultats_participants p
ON p.epr_id = e.epr_id AND p.pec_id = e.pec_id_original
WHERE p.is_cancelled = 0 AND p.epr_id IN (" . $strIds . ")
GROUP BY p.epr_id
";
$tabVendus = $objDatabase->fxGetResults($sqlVendus);
if (is_array($tabVendus)) {
for ($i = 1; $i <= count($tabVendus); $i++) {
$arrVendus[intval($tabVendus[$i]['epr_id'])] = intval($tabVendus[$i]['nb']);
}
}
// ---- Check-in (dossards remis) ----
$arrCheckin = array();
$sqlCheckin = "
SELECT epr_id, COUNT(*) AS nb
FROM resultats_participants
WHERE no_bib_remis = 1 AND epr_id IN (" . $strIds . ")
GROUP BY epr_id
";
$tabCheckin = $objDatabase->fxGetResults($sqlCheckin);
if (is_array($tabCheckin)) {
for ($i = 1; $i <= count($tabCheckin); $i++) {
$arrCheckin[intval($tabCheckin[$i]['epr_id'])] = intval($tabCheckin[$i]['nb']);
}
}
// ---- Liste d'attente : nombre + statut actif ----
$arrAttenteNb = array();
$sqlAttente = "
SELECT epr_id, COUNT(1) AS nb
FROM inscriptions_liste_attente_participants
WHERE (lispar_statut = 1 OR lispar_statut = 2) AND epr_id IN (" . $strIds . ")
GROUP BY epr_id
";
$tabAttente = $objDatabase->fxGetResults($sqlAttente);
if (is_array($tabAttente)) {
for ($i = 1; $i <= count($tabAttente); $i++) {
$arrAttenteNb[intval($tabAttente[$i]['epr_id'])] = intval($tabAttente[$i]['nb']);
}
}
$arrAttenteActive = array();
$sqlAttenteActive = "
SELECT epr_id, lis_active
FROM inscriptions_liste_attente
WHERE epr_id IN (" . $strIds . ")
";
$tabAttenteActive = $objDatabase->fxGetResults($sqlAttenteActive);
if (is_array($tabAttenteActive)) {
for ($i = 1; $i <= count($tabAttenteActive); $i++) {
$arrAttenteActive[intval($tabAttenteActive[$i]['epr_id'])] = intval($tabAttenteActive[$i]['lis_active']);
}
}
// ---- Prix (paliers publics) : prix courant + date du prochain palier ----
$arrTiers = array();
$sqlPrix = "
SELECT epr_id, ep_montant, ep_montant_affiche, ep_date_limite
FROM inscriptions_epreuves_prix
WHERE ep_prive = 0 AND epr_id IN (" . $strIds . ")
ORDER BY epr_id, ep_date_limite ASC
";
$tabPrix = $objDatabase->fxGetResults($sqlPrix);
if (is_array($tabPrix)) {
for ($i = 1; $i <= count($tabPrix); $i++) {
$intEpr = intval($tabPrix[$i]['epr_id']);
$fltMontant = (floatval($tabPrix[$i]['ep_montant_affiche']) > 0)
? floatval($tabPrix[$i]['ep_montant_affiche'])
: floatval($tabPrix[$i]['ep_montant']);
if (!isset($arrTiers[$intEpr])) {
$arrTiers[$intEpr] = array();
}
$arrTiers[$intEpr][] = array(
'montant' => $fltMontant,
'date_limite' => trim((string)($tabPrix[$i]['ep_date_limite'] ?? '')),
);
}
}
$strToday = date('Y-m-d');
// ---- Assemblage par epreuve + agregat flash ----
$arrEpreuves = array();
$intFlashVendus = 0;
$intFlashMax = 0;
$intFlashAttente = 0;
$intFlashCompletes = 0;
$intFlashPresque = 0;
$fltFlashRevenus = 0.0;
$strFlashProchaineLimite = '';
for ($i = 1; $i <= count($tabEpreuves); $i++) {
$row = $tabEpreuves[$i];
$intEpr = intval($row['epr_id']);
$strTitre = trim((string)($row['epr_type_' . $strLang] ?? ''));
if ($strTitre === '') {
$strTitre = trim((string)($row['epr_type_' . $strAlt] ?? ''));
}
$strNom = trim((string)($row['epr_nom_' . $strLang] ?? ''));
if ($strNom === '') {
$strNom = trim((string)($row['epr_nom_' . $strAlt] ?? ''));
}
$strCategorie = trim((string)($row['epr_categorie_' . $strLang] ?? ''));
if ($strTitre === '') {
$strTitre = ($strNom !== '') ? $strNom : ('#' . $intEpr);
}
$intVendus = $arrVendus[$intEpr] ?? 0;
$intRestant = intval($row['epr_qte']);
$blnIllimitee = (intval($row['epr_qte_limitee']) !== 1);
// Max courant = places vendues + restantes ; origine = max initial fixe.
$intMax = $intVendus + $intRestant;
$intOrigine = intval($row['epr_qte_origine']);
$fltPct = ($blnIllimitee || $intMax <= 0) ? 0 : round(($intVendus / $intMax) * 100);
if ($fltPct > 100) {
$fltPct = 100;
}
// Etat de remplissage : complete / presque / ok / illimitee
if ($blnIllimitee) {
$strEtat = 'illimitee';
} elseif ($intRestant <= 0) {
$strEtat = 'complete';
$intFlashCompletes++;
} elseif ($fltPct >= 90) {
$strEtat = 'presque';
$intFlashPresque++;
} elseif ($fltPct >= 75) {
$strEtat = 'attention';
} else {
$strEtat = 'ok';
}
// Prix courant + date du prochain palier
$fltPrixCourant = null;
$strPrixDateFin = '';
$fltPrixProchain = null;
if (!empty($arrTiers[$intEpr])) {
$arrEprTiers = $arrTiers[$intEpr];
$intIdxCourant = -1;
foreach ($arrEprTiers as $intIdx => $arrTier) {
if ($arrTier['date_limite'] !== '' && $arrTier['date_limite'] >= $strToday) {
$intIdxCourant = $intIdx;
break;
}
}
if ($intIdxCourant === -1) {
// Tous les paliers sont passes : on prend le dernier (prix final), pas de prochain.
$intIdxCourant = count($arrEprTiers) - 1;
}
$fltPrixCourant = $arrEprTiers[$intIdxCourant]['montant'];
$strPrixDateFin = $arrEprTiers[$intIdxCourant]['date_limite'];
if (isset($arrEprTiers[$intIdxCourant + 1])) {
$fltPrixProchain = $arrEprTiers[$intIdxCourant + 1]['montant'];
}
}
$intAttenteNb = $arrAttenteNb[$intEpr] ?? 0;
$blnAttenteActive = !empty($arrAttenteActive[$intEpr]);
// Agregat flash
$intFlashVendus += $intVendus;
if (!$blnIllimitee) {
$intFlashMax += $intMax;
}
$intFlashAttente += $intAttenteNb;
if ($fltPrixCourant !== null) {
$fltFlashRevenus += $fltPrixCourant * $intVendus;
}
// Prochaine date limite d'inscription (la plus proche, dans le futur)
$strLimiteInscr = trim((string)($row['epr_date_limite'] ?? ''));
if ($strLimiteInscr !== '' && strpos($strLimiteInscr, '0000') !== 0 && $strLimiteInscr >= $strToday) {
if ($strFlashProchaineLimite === '' || $strLimiteInscr < $strFlashProchaineLimite) {
$strFlashProchaineLimite = $strLimiteInscr;
}
}
$arrEpreuves[] = array(
'epr_id' => $intEpr,
'titre' => $strTitre,
'nom' => $strNom,
'categorie' => $strCategorie,
'date' => trim((string)($row['epr_date'] ?? '')),
'heure' => trim((string)($row['epr_heure'] ?? '')),
'date_limite_inscr' => $strLimiteInscr,
'equipe' => (intval($row['epr_equipe']) === 1),
'nb_participants' => intval($row['epr_nb_participants']),
'vendus' => $intVendus,
'restant' => $intRestant,
'max' => $intMax,
'origine' => $intOrigine,
'illimitee' => $blnIllimitee,
'pct' => $fltPct,
'etat' => $strEtat,
'checkin' => $arrCheckin[$intEpr] ?? 0,
'attente_nb' => $intAttenteNb,
'attente_active' => $blnAttenteActive,
'prix_courant' => $fltPrixCourant,
'prix_date_fin' => $strPrixDateFin,
'prix_prochain' => $fltPrixProchain,
);
}
$intPctGlobal = ($intFlashMax > 0) ? round(($intFlashVendus / $intFlashMax) * 100) : 0;
if ($intPctGlobal > 100) {
$intPctGlobal = 100;
}
$arrFlash = array(
'total_vendus' => $intFlashVendus,
'total_max' => $intFlashMax,
'total_restant' => max(0, $intFlashMax - $intFlashVendus),
'pct_global' => $intPctGlobal,
'revenus' => $fltFlashRevenus,
'nb_epreuves' => count($arrEpreuves),
'nb_completes' => $intFlashCompletes,
'nb_presque' => $intFlashPresque,
'total_attente' => $intFlashAttente,
'prochaine_date_limite' => $strFlashProchaineLimite,
);
return array('epreuves' => $arrEpreuves, 'flash' => $arrFlash);
}
/**
* Rendu visuel du panneau de stats (bande flash + tuiles par epreuve).
* Toujours visible (pas de collapse), s'affiche a droite de la carte.
*/
function fxPromoteurHubRenderStatsPanel($arrStats, $strLangue = 'fr')
{
if (empty($arrStats) || empty($arrStats['epreuves'])) {
return '';
}
$blnFr = ($strLangue !== 'en');
$arrFlash = $arrStats['flash'];
$lbl = function ($fr, $en) use ($blnFr) {
return $blnFr ? $fr : $en;
};
$html = '<div class="promoteur-hub-stats">';
// ---- Bande flash ----
$html .= '<div class="promoteur-hub-flash">';
$html .= '<div class="phub-flash-item phub-flash-main">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['total_vendus']) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('inscrits', 'registered')) . '</span>';
$html .= '</div>';
if (intval($arrFlash['total_max']) > 0) {
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['pct_global']) . '%</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('rempli', 'filled')) . '</span>';
$html .= '</div>';
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . intval($arrFlash['total_restant']) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('places restantes', 'spots left')) . '</span>';
$html .= '</div>';
}
if (floatval($arrFlash['revenus']) > 0) {
$html .= '<div class="phub-flash-item">';
$html .= '<span class="phub-flash-num">' . fxPromoteurHubEsc(fxShowPrix($arrFlash['revenus'], $strLangue)) . '</span>';
$html .= '<span class="phub-flash-lbl">' . fxPromoteurHubEsc($lbl('revenus est.', 'est. revenue')) . '</span>';
$html .= '</div>';
}
$html .= '</div>'; // .promoteur-hub-flash
// ---- Badges d'alerte ----
$arrBadges = array();
if (intval($arrFlash['nb_completes']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-danger', 'icon' => 'fa-ban',
'txt' => intval($arrFlash['nb_completes']) . ' ' . $lbl('complète(s)', 'sold out'));
}
if (intval($arrFlash['nb_presque']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-warn', 'icon' => 'fa-exclamation-triangle',
'txt' => intval($arrFlash['nb_presque']) . ' ' . $lbl('presque pleine(s)', 'almost full'));
}
if (intval($arrFlash['total_attente']) > 0) {
$arrBadges[] = array('cls' => 'phub-badge-info', 'icon' => 'fa-hourglass-half',
'txt' => intval($arrFlash['total_attente']) . ' ' . $lbl('en attente', 'waiting'));
}
if ($arrFlash['prochaine_date_limite'] !== '') {
$arrBadges[] = array('cls' => 'phub-badge-soft', 'icon' => 'fa-clock-o',
'txt' => $lbl('limite', 'deadline') . ' ' . fxShowDate($arrFlash['prochaine_date_limite'], $strLangue, 1));
}
if (!empty($arrBadges)) {
$html .= '<div class="promoteur-hub-badges">';
foreach ($arrBadges as $arrBadge) {
$html .= '<span class="phub-badge ' . $arrBadge['cls'] . '"><i class="fa ' . $arrBadge['icon'] . ' mr-1" aria-hidden="true"></i>'
. fxPromoteurHubEsc($arrBadge['txt']) . '</span>';
}
$html .= '</div>';
}
// ---- Tuiles par epreuve ----
$html .= '<div class="promoteur-hub-epreuves-grid">';
foreach ($arrStats['epreuves'] as $arrEpr) {
$strEtat = $arrEpr['etat'];
$html .= '<div class="phub-epr-tile phub-etat-' . fxPromoteurHubEsc($strEtat) . '">';
// En-tete : titre + categorie
$html .= '<div class="phub-epr-head">';
$html .= '<span class="phub-epr-titre">' . fxPromoteurHubEsc($arrEpr['titre']) . '</span>';
if ($arrEpr['categorie'] !== '') {
$html .= '<span class="phub-epr-cat">' . fxPromoteurHubEsc($arrEpr['categorie']) . '</span>';
}
if ($arrEpr['nom'] !== '' && $arrEpr['nom'] !== $arrEpr['titre']) {
$html .= '<span class="phub-epr-sub">' . fxPromoteurHubEsc($arrEpr['nom']) . '</span>';
}
$html .= '</div>';
// Gros chiffre : nombre de dossards vendus
$html .= '<div class="phub-epr-big">';
$html .= '<span class="phub-epr-bignum">' . intval($arrEpr['vendus']) . '</span>';
if (!$arrEpr['illimitee']) {
$html .= '<span class="phub-epr-max">/ ' . intval($arrEpr['max']) . '</span>';
}
$html .= '<span class="phub-epr-biglbl">' . fxPromoteurHubEsc($blnFr ? 'dossards vendus' : 'bibs sold') . '</span>';
$html .= '</div>';
// Barre de progression
if (!$arrEpr['illimitee'] && intval($arrEpr['max']) > 0) {
$html .= '<div class="phub-epr-bar"><span style="width:' . intval($arrEpr['pct']) . '%"></span></div>';
$html .= '<div class="phub-epr-barlbl">' . intval($arrEpr['pct']) . '% &middot; '
. intval($arrEpr['restant']) . ' ' . fxPromoteurHubEsc($blnFr ? 'restant' : 'left') . '</div>';
} else {
$html .= '<div class="phub-epr-barlbl phub-epr-illimite"><span class="phub-infini mr-1" aria-hidden="true">&infin;</span>'
. fxPromoteurHubEsc($blnFr ? 'illimité' : 'unlimited') . '</div>';
}
// Pastilles secondaires
$html .= '<div class="phub-epr-chips">';
if ($arrEpr['prix_courant'] !== null) {
$html .= '<span class="phub-chip phub-chip-prix" title="' . fxPromoteurHubEsc($blnFr ? 'Prix courant' : 'Current price') . '">'
. '<i class="fa fa-tag mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_courant'], $strLangue)) . '</span>';
if ($arrEpr['prix_prochain'] !== null && $arrEpr['prix_date_fin'] !== '' && $arrEpr['prix_date_fin'] >= date('Y-m-d')) {
$html .= '<span class="phub-chip phub-chip-next" title="' . fxPromoteurHubEsc($blnFr ? 'Prochain prix' : 'Next price') . '">'
. '<i class="fa fa-arrow-up mr-1" aria-hidden="true"></i>'
. fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_prochain'], $strLangue))
. ' ' . fxPromoteurHubEsc($blnFr ? 'dès' : 'on') . ' '
. fxShowDate($arrEpr['prix_date_fin'], $strLangue, 1) . '</span>';
}
}
if (intval($arrEpr['checkin']) > 0) {
$html .= '<span class="phub-chip phub-chip-checkin" title="' . fxPromoteurHubEsc($blnFr ? 'Dossards remis' : 'Bibs handed out') . '">'
. '<i class="fa fa-check mr-1" aria-hidden="true"></i>' . intval($arrEpr['checkin']) . '</span>';
}
if (intval($arrEpr['attente_nb']) > 0 || $arrEpr['attente_active']) {
$strAtt = $arrEpr['attente_active'] && intval($arrEpr['attente_nb']) === 0
? ($blnFr ? 'liste active' : 'list active')
: intval($arrEpr['attente_nb']) . ' ' . ($blnFr ? 'en attente' : 'waiting');
$html .= '<span class="phub-chip phub-chip-attente" title="' . fxPromoteurHubEsc($blnFr ? 'Liste d\'attente' : 'Waiting list') . '">'
. '<i class="fa fa-hourglass-half mr-1" aria-hidden="true"></i>' . fxPromoteurHubEsc($strAtt) . '</span>';
}
if ($arrEpr['date'] !== '' && strpos($arrEpr['date'], '0000') !== 0) {
$html .= '<span class="phub-chip phub-chip-date"><i class="fa fa-calendar-o mr-1" aria-hidden="true"></i>'
. fxShowDate($arrEpr['date'], $strLangue, 1) . '</span>';
}
$html .= '</div>'; // .phub-epr-chips
$html .= '</div>'; // .phub-epr-tile
}
$html .= '</div>'; // .promoteur-hub-epreuves-grid
$html .= '</div>'; // .promoteur-hub-stats
return $html;
}
function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = false)
{
$strCardClass = 'card promoteur-hub-card mb-3' . ($blnArchived ? ' promoteur-hub-card-archived' : '');
// Stats par epreuve (a droite). Vide pour les dons / evenements sans epreuve.
$strStats = fxPromoteurHubRenderStatsPanel(
fxPromoteurHubGetEpreuveStats(intval($arrItem['eve_id']), $strLangue),
$strLangue
);
$blnHasStats = ($strStats !== '');
$html = '<div class="' . fxPromoteurHubEsc($strCardClass) . '">';
$html .= '<div class="card-body">';
$html .= '<div class="row promoteur-hub-card-row">';
// ---- Colonne gauche : identite + bouton tableau de bord ----
$html .= '<div class="' . ($blnHasStats ? 'col-12 col-lg-5' : 'col-12') . ' promoteur-hub-card-main">';
$html .= '<div class="d-flex align-items-center flex-wrap mb-2">';
$html .= '<div class="promoteur-hub-card-icon mr-3"><img src="' . fxPromoteurHubEsc($arrItem['icon']) . '" width="64" alt=""></div>';
$html .= '<div class="flex-grow-1">';
@ -521,6 +962,14 @@ function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = fals
$html .= fxPromoteurHubRenderEventPanel($arrItem, $strLangue);
}
$html .= '</div>'; // .promoteur-hub-card-main
// ---- Colonne droite : stats par epreuve (toujours visibles) ----
if ($blnHasStats) {
$html .= '<div class="col-12 col-lg-7 promoteur-hub-card-stats">' . $strStats . '</div>';
}
$html .= '</div>'; // .promoteur-hub-card-row
$html .= '</div></div>';
return $html;

View File

@ -7,7 +7,7 @@
* Constantes *
*
**************/
define('_VERSION_CODE', '4.72.708');
define('_VERSION_CODE', '4.72.709');
define('_DATE_CODE', '2026-06-27');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');