Enhance promoteur hub functionality with AJAX loading and access control improvements

This commit introduces significant updates to the promoteur hub, including the addition of a new JavaScript file for AJAX handling of event statistics and links. The PHP file `ajax_promoteur_hub.php` is modified to support new actions for fetching event statistics and links, improving the user experience with better error handling and loading indicators. Additionally, a new batch access control function is implemented in `inc_fx_promoteur_hub.php` to streamline permission checks for events. The version code is incremented to reflect these enhancements, aiming to improve functionality and maintainability across the promoteur hub.
This commit is contained in:
2026-07-08 08:48:23 -04:00
parent dd4a83043a
commit 4522ddde8d
5 changed files with 280 additions and 77 deletions

View File

@ -16,22 +16,36 @@ if (empty($_SESSION['com_id'])) {
exit;
}
if ($strAction !== 'event_stats' || $intEveId <= 0) {
$intComId = intval($_SESSION['com_id']);
if (!in_array($strAction, array('event_stats', 'event_links'), true) || $intEveId <= 0) {
echo json_encode(array('ok' => false, 'msg' => 'Requête invalide'));
exit;
}
$intComId = intval($_SESSION['com_id']);
if (!fxPromoteurHubComHasEvent($intComId, $intEveId)) {
echo json_encode(array('ok' => false, 'msg' => 'Accès refusé'));
exit;
}
$arrBundle = fxPromoteurHubRenderEventStatsBundle($intEveId, $strLangue);
if ($strAction === 'event_stats') {
$arrBundle = fxPromoteurHubRenderEventStatsBundle($intEveId, $strLangue);
echo json_encode(array(
echo json_encode(array(
'ok' => true,
'flash' => $arrBundle['flash'],
'html' => $arrBundle['html'],
));
exit;
}
$arrItem = fxPromoteurHubGetEventItemForLinks($intComId, $intEveId);
if ($arrItem === null) {
echo json_encode(array('ok' => false, 'msg' => 'Événement introuvable'));
exit;
}
echo json_encode(array(
'ok' => true,
'html' => fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue),
));

111
js/v2/promoteur-hub.js Normal file
View File

@ -0,0 +1,111 @@
/**
* Hub promoteur — chargement AJAX stats + menu fonctionnalités.
* Chargé uniquement sur compte_promoteur_hub.
*/
(function () {
'use strict';
var cfg = window.MS1_V2_PROMOTEUR_HUB || {};
var $ = window.jQuery;
if (!$) {
return;
}
var strAjaxUrl = cfg.ajaxUrl || '';
var strWaitMsg = cfg.preloaderWait || 'Chargement…';
var intHubLoaderPending = 0;
function fxHubShowLoader() {
intHubLoaderPending++;
if (intHubLoaderPending !== 1) {
return;
}
if (window.Ms1Loader) {
Ms1Loader.show(strWaitMsg);
return;
}
var $preloader = $('#preloader');
if ($preloader.length) {
$('#preloader_text').html(strWaitMsg);
$preloader.show();
}
}
function fxHubHideLoader() {
intHubLoaderPending = Math.max(0, intHubLoaderPending - 1);
if (intHubLoaderPending !== 0) {
return;
}
if (window.Ms1Loader) {
Ms1Loader.hide();
return;
}
$('#preloader').fadeOut('slow');
}
function fxHubLoadStats($collapse) {
if ($collapse.data('hub-loaded') || !strAjaxUrl) {
return;
}
var intEve = $collapse.data('hub-eve-id');
var strLang = $collapse.data('hub-lang') || 'fr';
fxHubShowLoader();
$.getJSON(strAjaxUrl, { a: 'event_stats', eve_id: intEve, lang: strLang })
.done(function (r) {
if (!r || !r.ok) {
$collapse.find('.promoteur-hub-stats-placeholder').text(r && r.msg ? r.msg : 'Erreur');
return;
}
$collapse.find('.promoteur-hub-stats-placeholder').replaceWith(r.html || '');
if (r.flash) {
$collapse.closest('.promoteur-hub-card').find('.promoteur-hub-card-flash-wrap').html(r.flash);
}
$collapse.data('hub-loaded', 1);
})
.fail(function () {
$collapse.find('.promoteur-hub-stats-placeholder').text('Erreur chargement');
})
.always(function () {
fxHubHideLoader();
});
}
function fxHubLoadLinks($collapse) {
if ($collapse.data('hub-links-loaded') || !strAjaxUrl) {
return;
}
var intEve = $collapse.data('hub-eve-id');
var strLang = $collapse.data('hub-lang') || 'fr';
fxHubShowLoader();
$.getJSON(strAjaxUrl, { a: 'event_links', eve_id: intEve, lang: strLang })
.done(function (r) {
if (!r || !r.ok) {
$collapse.find('.promoteur-hub-links-placeholder').text(r && r.msg ? r.msg : 'Erreur');
return;
}
$collapse.find('.promoteur-hub-links-placeholder').replaceWith(r.html || '');
$collapse.data('hub-links-loaded', 1);
})
.fail(function () {
$collapse.find('.promoteur-hub-links-placeholder').text('Erreur chargement');
})
.always(function () {
fxHubHideLoader();
});
}
$(document).on('show.bs.collapse', '.promoteur-hub-epreuves-collapse', function () {
var $card = $(this).closest('.promoteur-hub-card');
$card.find('.promoteur-hub-epreuves-collapse.show, .promoteur-hub-functions.show').not(this).collapse('hide');
fxHubLoadStats($(this));
});
$(document).on('show.bs.collapse', '.promoteur-hub-functions', function () {
var $card = $(this).closest('.promoteur-hub-card');
$card.find('.promoteur-hub-epreuves-collapse.show, .promoteur-hub-functions.show').not(this).collapse('hide');
fxHubLoadLinks($(this));
});
})();

View File

@ -130,6 +130,33 @@ function fxPromoteurHubCanAccessPromoteurWeb($intComId, $intEveId, $arrLegacyFli
return fxEveAccesHasAnyPermission(intval($intComId), intval($intEveId), fxPromoteurHubPermKeysWeb());
}
/**
* Contrôle d'accès promoteur web en lot (évite N requêtes eve_acces par carte).
* Prérequis : $arrLegacyFlip, $arrV2EventFlip, $arrWebPermFlip préchargés.
*/
function fxPromoteurHubCanAccessPromoteurWebBatch($intComId, $intEveId, $arrLegacyFlip, $arrV2EventFlip, $arrWebPermFlip)
{
$intEveId = intval($intEveId);
if (isset($arrLegacyFlip[$intEveId])) {
return true;
}
if (!function_exists('fxIsPromoteur')) {
require_once __DIR__ . '/inc_fonctions.php';
}
if (fxIsPromoteur(intval($intComId), $intEveId)) {
return true;
}
if (!isset($arrV2EventFlip[$intEveId])) {
return false;
}
return isset($arrWebPermFlip[$intEveId]);
}
function fxPromoteurHubParseDateTs($strDate)
{
$strDate = trim((string)$strDate);
@ -248,10 +275,9 @@ function fxPromoteurHubGetEvents($intComId, $strLangue = 'fr')
$arrArchived = array();
// Union : evenements legacy (com_eve_promoteur) + acces v2
$arrEventIds = array_merge(
fxPromoteurHubGetLegacyEventIds(intval($intComId)),
fxEveAccesGetEventIds(intval($intComId))
);
$arrLegacyIds = fxPromoteurHubGetLegacyEventIds(intval($intComId));
$arrV2EventIds = fxEveAccesGetEventIds(intval($intComId));
$arrEventIds = array_merge($arrLegacyIds, $arrV2EventIds);
if (empty($arrEventIds)) {
return array('highlight' => $arrHighlight, 'archived' => $arrArchived);
@ -269,7 +295,9 @@ function fxPromoteurHubGetEvents($intComId, $strLangue = 'fr')
$strAlt = ($strLang === 'fr') ? 'en' : 'fr';
$arrRoles = fxPromoteurHubGetRoleLabelsByEvent(intval($intComId));
$intToday = strtotime(date('Y-m-d'));
$arrLegacyFlip = array_flip(array_map('intval', fxPromoteurHubGetLegacyEventIds(intval($intComId))));
$arrLegacyFlip = array_flip(array_map('intval', $arrLegacyIds));
$arrV2EventFlip = array_flip(array_map('intval', $arrV2EventIds));
$arrWebPermFlip = array_flip(fxEveAccesGetEventIdsWithAnyPermission(intval($intComId), fxPromoteurHubPermKeysWeb()));
if (!function_exists('fxInscrGestionGetPromoteurEveIds')) {
require_once __DIR__ . '/inc_fx_inscriptions_gestion.php';
@ -328,7 +356,7 @@ function fxPromoteurHubGetEvents($intComId, $strLangue = 'fr')
'qte_modifiable' => intval($row['eve_qte_modifiable'] ?? 0),
'rapport_capitaines' => intval($row['eve_rapport_finances_capitaines'] ?? 0),
'com_id' => intval($intComId),
'url_promoteur' => fxPromoteurHubCanAccessPromoteurWeb($intComId, $intEveId, $arrLegacyFlip)
'url_promoteur' => fxPromoteurHubCanAccessPromoteurWebBatch($intComId, $intEveId, $arrLegacyFlip, $arrV2EventFlip, $arrWebPermFlip)
? fxPromoteurHubPromoteurTableUrl($intEveId, $strLang)
: '',
'url_mobile' => '',
@ -492,13 +520,11 @@ function fxPromoteurHubRenderLinkList($arrLinks, $strDefaultIcon)
return $html;
}
function fxPromoteurHubRenderEventPanel($arrItem, $strLangue)
function fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue)
{
$strPanelId = 'hub-fn-' . intval($arrItem['eve_id']);
$arrLinks = fxPromoteurHubGetEventLinks($arrItem, $strLangue);
$html = '<div class="collapse promoteur-hub-functions mt-3" id="' . fxPromoteurHubEsc($strPanelId) . '">';
$html .= '<div class="row">';
$html = '<div class="row">';
// Colonne gauche : Gestion (promoteur). Colonne droite : Rapports.
$html .= '<div class="col-12 col-lg-6 mb-3">';
@ -512,11 +538,71 @@ function fxPromoteurHubRenderEventPanel($arrItem, $strLangue)
$html .= '</div>';
$html .= '</div>';
return $html;
}
/** Coquille du menu fonctionnalités — contenu chargé en AJAX au premier clic. */
function fxPromoteurHubRenderEventPanelShell($arrItem, $strLangue)
{
$strPanelId = 'hub-fn-' . intval($arrItem['eve_id']);
$intEveId = intval($arrItem['eve_id']);
$blnFr = ($strLangue !== 'en');
$strLoadMsg = $blnFr ? 'Chargement du menu…' : 'Loading menu…';
global $vDomaine;
$strRunnerSrc = $vDomaine . '/images/ms1-runner-loader.gif?v=' . _VERSION_CODE;
$html = '<div class="collapse promoteur-hub-functions mt-3" id="' . fxPromoteurHubEsc($strPanelId) . '"'
. ' data-hub-eve-id="' . $intEveId . '" data-hub-lang="' . fxPromoteurHubEsc($strLangue) . '">';
$html .= '<div class="promoteur-hub-links-placeholder ms1-loader ms1-loader--inline" role="status" aria-live="polite" aria-busy="true">'
. '<img class="ms1-loader__runner" src="' . fxPromoteurHubEsc($strRunnerSrc) . '" alt="" role="presentation" decoding="async">'
. '<span class="ms1-loader__label">' . fxPromoteurHubEsc($strLoadMsg) . '</span></div>';
$html .= '</div>';
return $html;
}
function fxPromoteurHubRenderEventPanel($arrItem, $strLangue)
{
$strPanelId = 'hub-fn-' . intval($arrItem['eve_id']);
$html = '<div class="collapse promoteur-hub-functions mt-3" id="' . fxPromoteurHubEsc($strPanelId) . '">';
$html .= fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue);
$html .= '</div>';
return $html;
}
/** Métadonnées événement pour le menu AJAX (1 requête légère). */
function fxPromoteurHubGetEventItemForLinks($intComId, $intEveId)
{
global $objDatabase;
$intEveId = intval($intEveId);
if ($intEveId <= 0 || !isset($objDatabase)) {
return null;
}
$row = $objDatabase->fxGetRow(
'SELECT eve_id, te_id, eve_qte_modifiable, eve_rapport_finances_capitaines
FROM inscriptions_evenements
WHERE eve_id = ' . $intEveId . ' AND eve_actif = 1'
);
if ($row === null) {
return null;
}
return array(
'eve_id' => intval($row['eve_id']),
'te_id' => intval($row['te_id'] ?? 0),
'qte_modifiable' => intval($row['eve_qte_modifiable'] ?? 0),
'rapport_capitaines' => intval($row['eve_rapport_finances_capitaines'] ?? 0),
'com_id' => intval($intComId),
);
}
/**
* Statistiques par epreuve pour un evenement (vendus / restant / max,
* check-in, liste d'attente, prix courant + date du prochain palier).
@ -989,13 +1075,27 @@ function fxPromoteurHubGetProduits($intEveId, $strLangue = 'fr')
$sql = "
SELECT p1.pro_id, p1.pro_nom_fr, p1.pro_nom_en, p1.pro_qte AS dispo,
(SELECT COUNT(p2.pro_id)
FROM resultats_produits_new p2, resultats_epreuves_commandees c
WHERE c.pec_id_original = p2.pec_id AND c.is_cancelled = 0 AND p2.pro_id = p1.pro_id) AS vendus,
(SELECT COUNT(p3.ppn_id)
FROM inscriptions_panier_produits_new p3, inscriptions_panier_acheteurs a
WHERE a.no_panier = p3.no_panier AND a.sta_id = 1 AND p3.pro_id = p1.pro_id) AS panier
COALESCE(v.vendus, 0) AS vendus,
COALESCE(pa.panier, 0) AS panier
FROM inscriptions_produits_new p1
LEFT JOIN (
SELECT p2.pro_id, COUNT(*) AS vendus
FROM resultats_produits_new p2
JOIN resultats_epreuves_commandees c
ON c.pec_id_original = p2.pec_id AND c.is_cancelled = 0
JOIN inscriptions_produits_new px
ON px.pro_id = p2.pro_id AND px.eve_id = " . $intEveId . "
GROUP BY p2.pro_id
) v ON v.pro_id = p1.pro_id
LEFT JOIN (
SELECT p3.pro_id, COUNT(*) AS panier
FROM inscriptions_panier_produits_new p3
JOIN inscriptions_panier_acheteurs a
ON a.no_panier = p3.no_panier AND a.sta_id = 1
JOIN inscriptions_produits_new px
ON px.pro_id = p3.pro_id AND px.eve_id = " . $intEveId . "
GROUP BY p3.pro_id
) pa ON pa.pro_id = p1.pro_id
WHERE p1.eve_id = " . $intEveId . " AND p1.pro_actif = 1
ORDER BY p1.pro_nom_" . $strLang . ", p1.pro_nom_fr
";
@ -1214,9 +1314,9 @@ function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = fals
$html .= '<div class="collapse promoteur-hub-epreuves-collapse mt-2" id="' . fxPromoteurHubEsc($strEprId) . '">' . $strGrid . $strProduits . '</div>';
}
// ---- Collapse : menu des fonctionnalites (gestion + rapports) ----
// ---- Collapse : menu des fonctionnalites (gestion + rapports) — chargé en AJAX ----
if ($blnFonctions) {
$html .= fxPromoteurHubRenderEventPanel($arrItem, $strLangue);
$html .= fxPromoteurHubRenderEventPanelShell($arrItem, $strLangue);
}
$html .= '</div></div>';
@ -1244,6 +1344,10 @@ function fxPromoteurHubRenderEventSection($arrItems, $strTitleClef, $strLangue,
function fxShowPromoteurHub($intComId, $strLangue = 'fr')
{
if (function_exists('fxV2RegisterScript')) {
fxV2RegisterScript('promoteur-hub');
}
$arrBuckets = fxPromoteurHubGetEvents(intval($intComId), $strLangue);
$html = '';
@ -1273,56 +1377,6 @@ function fxShowPromoteurHub($intComId, $strLangue = 'fr')
$html .= '<div class="alert alert-warning">' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_empty', 0)) . '</div>';
}
// Accordeon + chargement AJAX des stats au clic (volume prod sur resultats_*).
global $vDomaine;
$strAjaxStats = $vDomaine . '/ajax_promoteur_hub.php';
$strWaitMsg = afficheTexte('preloader_wait', 0, 0, 1);
if (trim((string)$strWaitMsg) === '' || $strWaitMsg === 'preloader_wait') {
$strWaitMsg = ($strLangue !== 'en') ? 'Chargement…' : 'Loading…';
}
$html .= '<script>(function ($) {'
. 'if (!$) { return; }'
. 'var strAjaxStats = ' . json_encode($strAjaxStats) . ';'
. 'var strWaitMsg = ' . json_encode($strWaitMsg) . ';'
. 'var intHubLoaderPending = 0;'
. 'function fxHubShowLoader() {'
. 'intHubLoaderPending++;'
. 'if (intHubLoaderPending !== 1) { return; }'
. 'if (window.Ms1Loader) { Ms1Loader.show(strWaitMsg); return; }'
. 'var $preloader = $("#preloader");'
. 'if ($preloader.length) { $("#preloader_text").html(strWaitMsg); $preloader.show(); }'
. '}'
. 'function fxHubHideLoader() {'
. 'intHubLoaderPending = Math.max(0, intHubLoaderPending - 1);'
. 'if (intHubLoaderPending !== 0) { return; }'
. 'if (window.Ms1Loader) { Ms1Loader.hide(); return; }'
. '$("#preloader").fadeOut("slow");'
. '}'
. 'function fxHubLoadStats($collapse) {'
. 'if ($collapse.data("hub-loaded")) { return; }'
. 'var intEve = $collapse.data("hub-eve-id");'
. 'var strLang = $collapse.data("hub-lang") || "fr";'
. 'fxHubShowLoader();'
. '$.getJSON(strAjaxStats, { a: "event_stats", eve_id: intEve, lang: strLang })'
. '.done(function (r) {'
. 'if (!r || !r.ok) { $collapse.find(".promoteur-hub-stats-placeholder").text(r && r.msg ? r.msg : "Erreur"); return; }'
. '$collapse.find(".promoteur-hub-stats-placeholder").replaceWith(r.html || "");'
. 'if (r.flash) { $collapse.closest(".promoteur-hub-card").find(".promoteur-hub-card-flash-wrap").html(r.flash); }'
. '$collapse.data("hub-loaded", 1);'
. '}).fail(function () { $collapse.find(".promoteur-hub-stats-placeholder").text("Erreur chargement"); })'
. '.always(function () { fxHubHideLoader(); });'
. '}'
. '$(document).on("show.bs.collapse", ".promoteur-hub-epreuves-collapse", function () {'
. 'var $card = $(this).closest(".promoteur-hub-card");'
. '$card.find(".promoteur-hub-epreuves-collapse.show, .promoteur-hub-functions.show").not(this).collapse("hide");'
. 'fxHubLoadStats($(this));'
. '});'
. '$(document).on("show.bs.collapse", ".promoteur-hub-functions", function () {'
. 'var $card = $(this).closest(".promoteur-hub-card");'
. '$card.find(".promoteur-hub-epreuves-collapse.show, .promoteur-hub-functions.show").not(this).collapse("hide");'
. '});'
. '})(window.jQuery);</script>';
return $html;
}

View File

@ -7,8 +7,8 @@
* Constantes *
*
**************/
define('_VERSION_CODE', '4.72.734');
define('_DATE_CODE', '2026-07-07');
define('_VERSION_CODE', '4.72.735');
define('_DATE_CODE', '2026-07-08');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');
// Outil temporaire ms1_kc_set.php — modifier key_chain_http (lien /kc/{pk}/)

View File

@ -6,7 +6,7 @@
if (!function_exists('fxV2RegisterScript')) {
/** @param string $strName bib-assignments | inscr-gestion */
/** @param string $strName bib-assignments | inscr-gestion | promoteur-hub */
function fxV2RegisterScript($strName) {
global $MS1_V2_SCRIPTS;
if (!isset($MS1_V2_SCRIPTS) || !is_array($MS1_V2_SCRIPTS)) {
@ -17,6 +17,22 @@ if (!function_exists('fxV2RegisterScript')) {
}
}
/** Config injectée dans window.MS1_V2_PROMOTEUR_HUB avant promoteur-hub.js */
function fxV2PromoteurHubConfig($strLangue) {
global $vDomaine;
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
$strWait = afficheTexte('preloader_wait', 0, 0, 1);
if (trim((string)$strWait) === '' || $strWait === 'preloader_wait') {
$strWait = ($strLangue === 'en') ? 'Loading…' : 'Chargement…';
}
return [
'ajaxUrl' => $vDomaine . '/ajax_promoteur_hub.php',
'preloaderWait' => $strWait,
];
}
/** Config injectée dans window.MS1_V2_INSCR_GESTION avant inscr-gestion.js */
function fxV2InscrGestionConfig($strLangue) {
global $vDomaine;
@ -52,8 +68,16 @@ if (!function_exists('fxV2RegisterScript')) {
$mapFiles = [
'bib-assignments' => '/js/v2/bib-assignments.js',
'inscr-gestion' => '/js/v2/inscr-gestion.js',
'promoteur-hub' => '/js/v2/promoteur-hub.js',
];
if (in_array('promoteur-hub', $MS1_V2_SCRIPTS, true)) {
$cfg = fxV2PromoteurHubConfig($strLangue ?? 'fr');
echo '<script>window.MS1_V2_PROMOTEUR_HUB='
. json_encode($cfg, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP)
. ';</script>' . "\n";
}
if (in_array('inscr-gestion', $MS1_V2_SCRIPTS, true)) {
$cfg = fxV2InscrGestionConfig($strLangue ?? 'fr');
echo '<script>window.MS1_V2_INSCR_GESTION='