diff --git a/ajax_promoteur_hub.php b/ajax_promoteur_hub.php index cb93e67..14c8583 100644 --- a/ajax_promoteur_hub.php +++ b/ajax_promoteur_hub.php @@ -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( + '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, - 'flash' => $arrBundle['flash'], - 'html' => $arrBundle['html'], + 'html' => fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue), )); diff --git a/js/v2/promoteur-hub.js b/js/v2/promoteur-hub.js new file mode 100644 index 0000000..5c409ea --- /dev/null +++ b/js/v2/promoteur-hub.js @@ -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)); + }); +})(); diff --git a/php/inc_fx_promoteur_hub.php b/php/inc_fx_promoteur_hub.php index 94d0b60..1966bcb 100644 --- a/php/inc_fx_promoteur_hub.php +++ b/php/inc_fx_promoteur_hub.php @@ -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 = '
'; - $html .= '
'; + $html = '
'; // Colonne gauche : Gestion (promoteur). Colonne droite : Rapports. $html .= '
'; @@ -512,11 +538,71 @@ function fxPromoteurHubRenderEventPanel($arrItem, $strLangue) $html .= '
'; $html .= '
'; + + 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 = '
'; + $html .= ''; $html .= '
'; return $html; } +function fxPromoteurHubRenderEventPanel($arrItem, $strLangue) +{ + $strPanelId = 'hub-fn-' . intval($arrItem['eve_id']); + + $html = '
'; + $html .= fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue); + $html .= '
'; + + 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 .= '
' . $strGrid . $strProduits . '
'; } - // ---- 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 .= '
'; @@ -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 .= '
' . fxPromoteurHubEsc(afficheTexte('promoteur_hub_empty', 0)) . '
'; } - // 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 .= ''; - return $html; } diff --git a/php/inc_settings.php b/php/inc_settings.php index b34c344..cac09a9 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -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}/) diff --git a/php/inc_v2_assets.php b/php/inc_v2_assets.php index f534ec5..c7c735e 100644 --- a/php/inc_v2_assets.php +++ b/php/inc_v2_assets.php @@ -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 '' . "\n"; + } + if (in_array('inscr-gestion', $MS1_V2_SCRIPTS, true)) { $cfg = fxV2InscrGestionConfig($strLangue ?? 'fr'); echo '