From 3e8112ec0b514ed0621c62edcd0d0637a2837d56 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 27 Jun 2026 17:03:26 -0400 Subject: [PATCH] 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. --- css/style.css | 211 ++++++++++++++++ php/inc_fx_promoteur_hub.php | 449 +++++++++++++++++++++++++++++++++++ php/inc_settings.php | 2 +- 3 files changed, 661 insertions(+), 1 deletion(-) diff --git a/css/style.css b/css/style.css index 2cc360a..f1d04d9 100644 --- a/css/style.css +++ b/css/style.css @@ -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; } diff --git a/php/inc_fx_promoteur_hub.php b/php/inc_fx_promoteur_hub.php index 6b8eb80..5d23f68 100644 --- a/php/inc_fx_promoteur_hub.php +++ b/php/inc_fx_promoteur_hub.php @@ -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 = '
'; + + // ---- Bande flash ---- + $html .= '
'; + + $html .= '
'; + $html .= '' . intval($arrFlash['total_vendus']) . ''; + $html .= '' . fxPromoteurHubEsc($lbl('inscrits', 'registered')) . ''; + $html .= '
'; + + if (intval($arrFlash['total_max']) > 0) { + $html .= '
'; + $html .= '' . intval($arrFlash['pct_global']) . '%'; + $html .= '' . fxPromoteurHubEsc($lbl('rempli', 'filled')) . ''; + $html .= '
'; + + $html .= '
'; + $html .= '' . intval($arrFlash['total_restant']) . ''; + $html .= '' . fxPromoteurHubEsc($lbl('places restantes', 'spots left')) . ''; + $html .= '
'; + } + + if (floatval($arrFlash['revenus']) > 0) { + $html .= '
'; + $html .= '' . fxPromoteurHubEsc(fxShowPrix($arrFlash['revenus'], $strLangue)) . ''; + $html .= '' . fxPromoteurHubEsc($lbl('revenus est.', 'est. revenue')) . ''; + $html .= '
'; + } + + $html .= '
'; // .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 .= '
'; + foreach ($arrBadges as $arrBadge) { + $html .= '' + . fxPromoteurHubEsc($arrBadge['txt']) . ''; + } + $html .= '
'; + } + + // ---- Tuiles par epreuve ---- + $html .= '
'; + + foreach ($arrStats['epreuves'] as $arrEpr) { + $strEtat = $arrEpr['etat']; + $html .= '
'; + + // En-tete : titre + categorie + $html .= '
'; + $html .= '' . fxPromoteurHubEsc($arrEpr['titre']) . ''; + if ($arrEpr['categorie'] !== '') { + $html .= '' . fxPromoteurHubEsc($arrEpr['categorie']) . ''; + } + if ($arrEpr['nom'] !== '' && $arrEpr['nom'] !== $arrEpr['titre']) { + $html .= '' . fxPromoteurHubEsc($arrEpr['nom']) . ''; + } + $html .= '
'; + + // Gros chiffre : nombre de dossards vendus + $html .= '
'; + $html .= '' . intval($arrEpr['vendus']) . ''; + if (!$arrEpr['illimitee']) { + $html .= '/ ' . intval($arrEpr['max']) . ''; + } + $html .= '' . fxPromoteurHubEsc($blnFr ? 'dossards vendus' : 'bibs sold') . ''; + $html .= '
'; + + // Barre de progression + if (!$arrEpr['illimitee'] && intval($arrEpr['max']) > 0) { + $html .= '
'; + $html .= '
' . intval($arrEpr['pct']) . '% · ' + . intval($arrEpr['restant']) . ' ' . fxPromoteurHubEsc($blnFr ? 'restant' : 'left') . '
'; + } else { + $html .= '
' + . fxPromoteurHubEsc($blnFr ? 'illimité' : 'unlimited') . '
'; + } + + // Pastilles secondaires + $html .= '
'; + + if ($arrEpr['prix_courant'] !== null) { + $html .= '' + . '' . fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_courant'], $strLangue)) . ''; + + if ($arrEpr['prix_prochain'] !== null && $arrEpr['prix_date_fin'] !== '' && $arrEpr['prix_date_fin'] >= date('Y-m-d')) { + $html .= '' + . '' + . fxPromoteurHubEsc(fxShowPrix($arrEpr['prix_prochain'], $strLangue)) + . ' ' . fxPromoteurHubEsc($blnFr ? 'dès' : 'on') . ' ' + . fxShowDate($arrEpr['prix_date_fin'], $strLangue, 1) . ''; + } + } + + if (intval($arrEpr['checkin']) > 0) { + $html .= '' + . '' . intval($arrEpr['checkin']) . ''; + } + + 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 .= '' + . '' . fxPromoteurHubEsc($strAtt) . ''; + } + + if ($arrEpr['date'] !== '' && strpos($arrEpr['date'], '0000') !== 0) { + $html .= '' + . fxShowDate($arrEpr['date'], $strLangue, 1) . ''; + } + + $html .= '
'; // .phub-epr-chips + + $html .= '
'; // .phub-epr-tile + } + + $html .= '
'; // .promoteur-hub-epreuves-grid + $html .= '
'; // .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 = '
'; $html .= '
'; + $html .= '
'; + + // ---- Colonne gauche : identite + bouton tableau de bord ---- + $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; @@ -521,6 +962,14 @@ function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = fals $html .= fxPromoteurHubRenderEventPanel($arrItem, $strLangue); } + $html .= '
'; // .promoteur-hub-card-main + + // ---- Colonne droite : stats par epreuve (toujours visibles) ---- + if ($blnHasStats) { + $html .= '
' . $strStats . '
'; + } + + $html .= '
'; // .promoteur-hub-card-row $html .= '
'; return $html; diff --git a/php/inc_settings.php b/php/inc_settings.php index 7a1c57b..55fc244 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -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');