From 21b28a0d06fea286316acea76806ec2d60317a37 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 8 Jul 2026 12:00:58 -0400 Subject: [PATCH] Implement PDF distribution panel for bib management (MSIN-4433) This commit introduces a new feature for generating and managing PDF distributions of bibs. It adds a new AJAX action to handle requests for the distribution panel, along with corresponding JavaScript functions to refresh and initialize the panel. CSS styles are updated to enhance the layout of the distribution panel, and new PHP functions are added to support the generation and metadata handling for the PDF prints. The version code is incremented to 4.72.759 to reflect these changes. --- ajax_bib_range.php | 24 + bib_dist_print.php | 46 ++ css/style.css | 42 ++ js/v2/bib-assignments.js | 102 ++++ php/inc_fx_promoteur.php | 757 +++++++++++++++++++++++++ php/inc_settings.php | 2 +- sql/MSIN-4433-bib-distribution-pdf.sql | 164 ++++++ 7 files changed, 1136 insertions(+), 1 deletion(-) create mode 100644 bib_dist_print.php create mode 100644 sql/MSIN-4433-bib-distribution-pdf.sql diff --git a/ajax_bib_range.php b/ajax_bib_range.php index af204f6..b63389a 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -43,6 +43,7 @@ $arrBibAjaxActions = [ 'batch_global_analysis', 'batch_global_simulate', 'global_batch_panel', + 'dist_print_panel', ]; if ($action !== '' && in_array($action, $arrBibAjaxActions, true)) { @@ -1153,6 +1154,29 @@ if ($action == 'qty_summary') { exit; } +// MSIN-4433 — Panneau impression PDF distribution dossards +if ($action == 'dist_print_panel') { + + $int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0); + + if ($int_eve_id <= 0) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_ajax_epr_invalid') + ]); + exit; + } + + $tabPanel = fxBibCollectDistPrintPanelData($int_eve_id, $strLangue); + + echo json_encode([ + 'success' => true, + 'html' => renderBibDistPrintPanel($int_eve_id, $strLangue, $tabPanel), + 'meta' => $tabPanel['meta'] ?? [], + ]); + exit; +} + // ===================== // ACTION INVALIDE // ===================== diff --git a/bib_dist_print.php b/bib_dist_print.php new file mode 100644 index 0000000..fa4a876 --- /dev/null +++ b/bib_dist_print.php @@ -0,0 +1,46 @@ + intMax) { + cb.checked = false; + } + }); + + let form = panel.querySelector('.bib-dist-print-form'); + if (form) { + form.addEventListener('submit', function () { + window.setTimeout(function () { + refreshBibDistPrintPanel(); + }, 1500); + }); + } + } + /** MSIN-4424 — Rafraîchit le sommaire des quantités (si déjà chargé ou ouverture). */ function refreshBibQtySummaryPanel() { let mount = document.getElementById('bib-qty-summary-mount'); diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 713cc07..926807d 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -3343,6 +3343,19 @@ function fxBibStaticFallback($clef) { 'bib_v4_qty_summary_by_epr' => ['fr' => 'Détail par épreuve', 'en' => 'Breakdown by race'], 'bib_v4_qty_summary_col_epr' => ['fr' => 'Épreuve', 'en' => 'Race'], 'bib_v4_ajax_global_sim_summary' => ['fr' => '%d épreuve(s) · %d dispo / %d à assigner → %d assignables (%d manque)', 'en' => '%d race(s) · %d avail. / %d to assign → %d assignable (%d short)'], + 'bib_v4_dist_print_title' => ['fr' => 'Impression PDF — distribution des dossards', 'en' => 'PDF print — bib distribution'], + 'bib_v4_dist_print_questions' => ['fr' => 'Colonnes optionnelles (questions)', 'en' => 'Optional columns (questions)'], + 'bib_v4_dist_print_generate' => ['fr' => 'Générer le PDF', 'en' => 'Generate PDF'], + 'bib_v4_dist_print_last_none' => ['fr' => 'Aucune impression enregistrée pour cet événement.', 'en' => 'No print recorded for this event yet.'], + 'bib_v4_dist_print_max_questions' => ['fr' => 'Maximum %d questions par épreuve pour le format lettre.', 'en' => 'Maximum %d questions per race for letter format.'], + 'bib_v4_dist_print_no_questions' => ['fr' => 'Aucune question éligible pour cette épreuve.', 'en' => 'No eligible question for this race.'], + 'bib_v4_dist_print_col_bib' => ['fr' => 'Dossard', 'en' => 'Bib'], + 'bib_v4_dist_print_col_nom' => ['fr' => 'Nom', 'en' => 'Last name'], + 'bib_v4_dist_print_col_prenom' => ['fr' => 'Prénom', 'en' => 'First name'], + 'bib_v4_dist_print_col_sexe' => ['fr' => 'Sexe', 'en' => 'Gender'], + 'bib_v4_dist_print_empty_epr' => ['fr' => 'Aucun dossard assigné pour cette épreuve.', 'en' => 'No bib assigned for this race.'], + 'bib_v4_dist_print_last' => ['fr' => 'Dernière impression : %s — %s', 'en' => 'Last print: %s — %s'], + 'bib_v4_dist_print_footer' => ['fr' => 'Imprimé le %s — %s', 'en' => 'Printed on %s — %s'], ]; return $tab[$clef][$lng] ?? ''; @@ -4609,6 +4622,10 @@ function fxShowBibTool4($str_code, $int_eve_id, $strLangue){ +
+ +
+ @@ -7723,4 +7740,744 @@ function fxResolveTeamBib( } return null; +} + +// ============================================================================= +// MSIN-4433 — Impression PDF distribution des dossards +// ============================================================================= + +/** MSIN-4433 — Limite de colonnes questions (format lettre 8½ × 11). */ +function fxBibDistPrintMaxQuestions() { + return 5; +} + +/** MSIN-4433 — Nom affiché du compte connecté (promoteur ou superadmin). */ +function fxBibGetCurrentPromoteurDisplayName() { + if (!empty($_SESSION['usa_id']) && !empty($_SESSION['usa_info']) && is_array($_SESSION['usa_info'])) { + $str = trim((string)($_SESSION['usa_info']['com_prenom'] ?? '') . ' ' . (string)($_SESSION['usa_info']['com_nom'] ?? '')); + if ($str !== '') { + return $str; + } + } + if (!empty($_SESSION['com_info']) && is_array($_SESSION['com_info'])) { + $str = trim((string)($_SESSION['com_info']['com_prenom'] ?? '') . ' ' . (string)($_SESSION['com_info']['com_nom'] ?? '')); + if ($str !== '') { + return $str; + } + } + return trim((string)($_SESSION['com_prenom'] ?? '') . ' ' . (string)($_SESSION['com_nom'] ?? '')); +} + +/** MSIN-4433 — Accès génération PDF (session, permissions, CSRF). */ +function fxBibRequireDistPrintAccess() { + if (!fxBibRequirePromoteurSession()) { + http_response_code(403); + header('Content-Type: text/plain; charset=utf-8'); + echo fxBibMsg('bib_v4_ajax_session_invalid'); + exit; + } + + if (empty($_SESSION['usa_id'])) { + require_once __DIR__ . '/inc_fx_eve_acces.php'; + $intComId = (int)($_SESSION['com_id'] ?? 0); + $intEveId = (int)($_REQUEST['eve_id'] ?? $_POST['eve_id'] ?? 0); + + if ($intComId <= 0 || $intEveId <= 0 + || !fxEveAccesHasAnyPermission($intComId, $intEveId, array('dossards.manage', 'epreuves.edit_qte'))) { + http_response_code(403); + header('Content-Type: text/plain; charset=utf-8'); + echo fxBibMsg('bib_v4_ajax_unauthorized'); + exit; + } + } + + $strToken = $_POST['csrf_token'] ?? $_REQUEST['csrf_token'] ?? ''; + if (!fxBibValidateCsrf($strToken)) { + http_response_code(403); + header('Content-Type: text/plain; charset=utf-8'); + echo fxBibMsg('bib_v4_ajax_unauthorized'); + exit; + } +} + +/** MSIN-4433 — Parse liste d'IDs questions depuis chaîne CSV. */ +function fxBibParseDistPrintQueIds($strCsv) { + if ($strCsv === null || $strCsv === '') { + return []; + } + $out = []; + foreach (explode(',', (string)$strCsv) as $part) { + $intId = (int)trim($part); + if ($intId > 0 && !in_array($intId, $out, true)) { + $out[] = $intId; + } + } + return $out; +} + +/** MSIN-4433 — Sérialise les IDs questions pour stockage. */ +function fxBibDistPrintQueIdsToCsv(array $tabQueIds) { + $out = []; + foreach ($tabQueIds as $mixId) { + $intId = (int)$mixId; + if ($intId > 0 && !in_array($intId, $out, true)) { + $out[] = $intId; + } + } + return implode(',', $out); +} + +/** MSIN-4433 — Libellé court sexe pour le PDF. */ +function fxBibDistPrintSexeLabel($strSexe, $strLangue = 'fr') { + $strSexe = strtolower(trim((string)$strSexe)); + if ($strLangue === 'en') { + $tab = ['f' => 'F', 'h' => 'M', 'n' => 'X', 'a' => 'O']; + } else { + $tab = ['f' => 'F', 'h' => 'H', 'n' => 'X', 'a' => 'A']; + } + return $tab[$strSexe] ?? strtoupper($strSexe); +} + +/** MSIN-4433 — Date/heure affichée (pied de page, panneau). */ +function fxBibFormatDistPrintDateTime($strWhen = '', $strLangue = 'fr') { + $ts = ($strWhen !== '') ? strtotime($strWhen) : time(); + if ($ts === false) { + $ts = time(); + } + if ($strLangue === 'en') { + return date('F j, Y, g:i a', $ts); + } + $tabMonths = [ + 1 => 'janvier', 2 => 'février', 3 => 'mars', 4 => 'avril', + 5 => 'mai', 6 => 'juin', 7 => 'juillet', 8 => 'août', + 9 => 'septembre', 10 => 'octobre', 11 => 'novembre', 12 => 'décembre', + ]; + $intDay = (int)date('j', $ts); + $intMonth = (int)date('n', $ts); + $intYear = (int)date('Y', $ts); + $strTime = date('H:i', $ts); + return $intDay . ' ' . ($tabMonths[$intMonth] ?? '') . ' ' . $intYear . ', ' . $strTime; +} + +/** MSIN-4433 — Métadonnées dernière impression (événement). */ +function fxBibGetDistPrintMeta($int_eve_id) { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + if ($int_eve_id <= 0) { + return ['at' => '', 'by' => '', 'com_id' => 0]; + } + + $row = $objDatabase->fxGetRow(" + SELECT eve_bib_dist_print_at, eve_bib_dist_print_by, eve_bib_dist_print_com_id + FROM inscriptions_evenements + WHERE eve_id = $int_eve_id + LIMIT 1 + "); + + if (!is_array($row)) { + return ['at' => '', 'by' => '', 'com_id' => 0]; + } + + return [ + 'at' => trim((string)($row['eve_bib_dist_print_at'] ?? '')), + 'by' => trim((string)($row['eve_bib_dist_print_by'] ?? '')), + 'com_id' => (int)($row['eve_bib_dist_print_com_id'] ?? 0), + ]; +} + +/** MSIN-4433 — Persiste date / auteur de l'impression. */ +function fxBibSaveDistPrintMeta($int_eve_id, $int_com_id, $str_by) { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + $int_com_id = (int)$int_com_id; + $str_by = trim((string)$str_by); + if ($int_eve_id <= 0) { + return; + } + + $sql = " + UPDATE inscriptions_evenements + SET eve_bib_dist_print_at = '" . $objDatabase->fxEscape(fxGetDateTime()) . "', + eve_bib_dist_print_by = '" . $objDatabase->fxEscape($str_by) . "', + eve_bib_dist_print_com_id = $int_com_id + WHERE eve_id = $int_eve_id + LIMIT 1 + "; + $objDatabase->fxQuery($sql); +} + +/** + * MSIN-4433 — Données panneau : épreuves, questions éligibles, sélection mémorisée. + * @return array{meta: array, epreuves: array>} + */ +function fxBibCollectDistPrintPanelData($int_eve_id, $strLangue = 'fr') { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + $tabOut = ['meta' => fxBibGetDistPrintMeta($int_eve_id), 'epreuves' => []]; + + if ($int_eve_id <= 0) { + return $tabOut; + } + + $sqlEpreuves = " + SELECT * + FROM inscriptions_epreuves + WHERE eve_id = $int_eve_id + AND epr_actif = 1 + ORDER BY epr_tri, epr_id + "; + $tabEpreuves = $objDatabase->fxGetResults($sqlEpreuves); + if (!is_array($tabEpreuves)) { + return $tabOut; + } + + foreach ($tabEpreuves as $epr) { + $epr_id = (int)($epr['epr_id'] ?? 0); + if ($epr_id <= 0) { + continue; + } + + $tabQuestionOpts = fxBibGetQuestionSortOptions($epr_id, $int_eve_id, $strLangue); + $tabSelected = fxBibParseDistPrintQueIds($epr['ba_bib_dist_que_ids'] ?? ''); + $tabValidIds = []; + foreach ($tabQuestionOpts as $opt) { + if (preg_match('/^que:(\d+)$/', (string)($opt['key'] ?? ''), $m)) { + $tabValidIds[] = (int)$m[1]; + } + } + $tabSelected = array_values(array_intersect($tabSelected, $tabValidIds)); + + $tabOut['epreuves'][] = [ + 'epr_id' => $epr_id, + 'epr_label' => fxBibAnomalyEprLabel($epr, $strLangue), + 'questions' => $tabQuestionOpts, + 'selected' => $tabSelected, + 'avec_bib' => (int)(fxInfosBibEpreuve($epr_id)['avec_bib'] ?? 0), + ]; + } + + return $tabOut; +} + +/** MSIN-4433 — Parse sélections POST dist_print[epr_id][] = que_id. */ +function fxBibParseDistPrintSelectionsFromPost(array $post) { + $tabRaw = $post['dist_print'] ?? []; + if (!is_array($tabRaw)) { + return []; + } + + $out = []; + foreach ($tabRaw as $mixEprId => $mixQueIds) { + $epr_id = (int)$mixEprId; + if ($epr_id <= 0) { + continue; + } + if (!is_array($mixQueIds)) { + $mixQueIds = [$mixQueIds]; + } + $tabIds = []; + foreach ($mixQueIds as $mixQueId) { + $intQueId = (int)$mixQueId; + if ($intQueId > 0 && !in_array($intQueId, $tabIds, true)) { + $tabIds[] = $intQueId; + } + } + $out[$epr_id] = $tabIds; + } + + return $out; +} + +/** + * MSIN-4433 — Sauvegarde les questions cochées par épreuve (à la génération PDF). + * @return array{success: bool, message?: string} + */ +function fxBibSaveDistPrintSelections($int_eve_id, array $tabSelections, $strLangue = 'fr') { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + if ($int_eve_id <= 0) { + return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')]; + } + + $intMax = fxBibDistPrintMaxQuestions(); + $tabPanel = fxBibCollectDistPrintPanelData($int_eve_id, $strLangue); + + foreach ($tabPanel['epreuves'] as $tabEpr) { + $epr_id = (int)($tabEpr['epr_id'] ?? 0); + if ($epr_id <= 0) { + continue; + } + + $tabValidIds = []; + foreach ($tabEpr['questions'] as $opt) { + if (preg_match('/^que:(\d+)$/', (string)($opt['key'] ?? ''), $m)) { + $tabValidIds[] = (int)$m[1]; + } + } + + $tabChosen = $tabSelections[$epr_id] ?? []; + if (!is_array($tabChosen)) { + $tabChosen = []; + } + + $tabFiltered = []; + foreach ($tabChosen as $intQueId) { + $intQueId = (int)$intQueId; + if ($intQueId > 0 && in_array($intQueId, $tabValidIds, true) + && !in_array($intQueId, $tabFiltered, true)) { + $tabFiltered[] = $intQueId; + } + } + + if (count($tabFiltered) > $intMax) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_dist_print_max_questions', $intMax), + ]; + } + + $strCsv = fxBibDistPrintQueIdsToCsv($tabFiltered); + $sql = " + UPDATE inscriptions_epreuves + SET ba_bib_dist_que_ids = '" . $objDatabase->fxEscape($strCsv) . "' + WHERE epr_id = $epr_id + AND eve_id = $int_eve_id + LIMIT 1 + "; + $objDatabase->fxQuery($sql); + } + + return ['success' => true]; +} + +/** + * MSIN-4433 — Participants avec dossard (une ligne par no_bib), tri batch. + * @return array> + */ +function fxBibGetParticipantsForDistPrint($epr_id, $sort1, $sort2 = '', $strLangue = 'fr') { + global $objDatabase; + + $epr_id = (int)$epr_id; + if ($epr_id <= 0) { + return []; + } + + $strSort1 = fxBibNormalizeSortKey($sort1); + $strSort2 = fxBibNormalizeSortKey($sort2); + if ($strSort1 === '') { + return []; + } + if ($strSort1 === $strSort2) { + $strSort2 = ''; + } + + $sqlBibNumP = fxBibSqlNoBibUnsigned('p.no_bib'); + $orderBy = fxBibBuildBatchOrderBy($epr_id, $strSort1, $strSort2, $strLangue); + + $sql = " + SELECT + p.par_id, + p.pec_id, + p.no_bib, + p.par_nom, + p.par_prenom, + p.par_sexe + FROM resultats_participants p + LEFT JOIN resultats_epreuves_commandees c + ON c.pec_id_original = p.pec_id + AND c.epr_id = p.epr_id + WHERE p.epr_id = $epr_id + AND p.is_cancelled = 0 + AND $sqlBibNumP > 0 + AND p.rol_id NOT IN (3, 4) + ORDER BY $orderBy + "; + + $rows = $objDatabase->fxGetResults($sql); + if (!is_array($rows)) { + return []; + } + + $tabSeen = []; + $tabOut = []; + foreach ($rows as $row) { + $strBibKey = (string)(int)($row['no_bib'] ?? 0); + if ($strBibKey === '0' || isset($tabSeen[$strBibKey])) { + continue; + } + $tabSeen[$strBibKey] = true; + $tabOut[] = $row; + } + + return $tabOut; +} + +/** + * MSIN-4433 — Réponses questions pour une liste de participants. + * @return array> clé "par_id:pec_id" => [que_id => réponse] + */ +function fxBibGetDistPrintQuestionAnswers($epr_id, array $tabQueIds, array $tabParticipants, $strLangue = 'fr') { + global $objDatabase; + + $epr_id = (int)$epr_id; + $tabQueIds = array_values(array_filter(array_map('intval', $tabQueIds))); + if ($epr_id <= 0 || empty($tabQueIds) || empty($tabParticipants)) { + return []; + } + + $tabParIds = []; + foreach ($tabParticipants as $row) { + $intParId = (int)($row['par_id'] ?? 0); + if ($intParId > 0) { + $tabParIds[] = $intParId; + } + } + $tabParIds = array_values(array_unique($tabParIds)); + if (empty($tabParIds)) { + return []; + } + + $strCol = ($strLangue === 'en') ? 'que_choix_en' : 'que_choix_fr'; + $strQueIds = implode(',', $tabQueIds); + $strParIds = implode(',', $tabParIds); + + $sql = " + SELECT par_id, pec_id, que_id, TRIM($strCol) AS ans + FROM resultats_questions + WHERE que_actif = 1 + AND que_id IN ($strQueIds) + AND par_id IN ($strParIds) + "; + $rows = $objDatabase->fxGetResults($sql); + if (!is_array($rows)) { + return []; + } + + $out = []; + foreach ($rows as $row) { + $strKey = (int)($row['par_id'] ?? 0) . ':' . (int)($row['pec_id'] ?? 0); + $intQueId = (int)($row['que_id'] ?? 0); + $out[$strKey][$intQueId] = trim((string)($row['ans'] ?? '')); + } + + return $out; +} + +/** MSIN-4433 — Tronque texte pour cellule PDF. */ +function fxBibDistPrintTruncate($str, $intMax = 28) { + $str = trim((string)$str); + if ($str === '') { + return ''; + } + if (function_exists('mb_strlen') && function_exists('mb_substr')) { + if (mb_strlen($str) <= $intMax) { + return $str; + } + return mb_substr($str, 0, max(1, $intMax - 1)) . '…'; + } + if (strlen($str) <= $intMax) { + return $str; + } + return substr($str, 0, max(1, $intMax - 1)) . '…'; +} + +/** MSIN-4433 — Génère et envoie le PDF (format lettre). */ +function fxBibOutputDistPrintPdf($int_eve_id, $strLangue = 'fr', $strPrintedBy = '') { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + if ($int_eve_id <= 0) { + http_response_code(400); + exit; + } + + $tabEvent = $objDatabase->fxGetRow("SELECT * FROM inscriptions_evenements WHERE eve_id = $int_eve_id LIMIT 1"); + if (!is_array($tabEvent)) { + http_response_code(404); + exit; + } + + $strEventTitle = trim((string)($tabEvent['eve_nom_' . $strLangue] ?? '')); + if ($strEventTitle === '') { + $strEventTitle = trim((string)($tabEvent['eve_nom_fr'] ?? '')); + } + + $strPrintedAt = fxBibFormatDistPrintDateTime('', $strLangue); + if (trim((string)$strPrintedBy) === '') { + $strPrintedBy = fxBibGetCurrentPromoteurDisplayName(); + } + $strFooter = sprintf( + fxBibTexte('bib_v4_dist_print_footer', 0), + $strPrintedAt, + $strPrintedBy + ); + + $tabPanel = fxBibCollectDistPrintPanelData($int_eve_id, $strLangue); + + $strDocRoot = $_SERVER['DOCUMENT_ROOT'] ?? dirname(__DIR__); + require_once($strDocRoot . '/fpdf182/fpdf.php'); + + if (!class_exists('BibDistPrintPdf', false)) { + class BibDistPrintPdf extends FPDF { + public $footerText = ''; + + function Footer() { + $this->SetY(-12); + $this->SetFont('Arial', 'I', 7); + $this->Cell(0, 4, utf8_decode($this->footerText), 0, 0, 'L'); + $this->Cell(0, 4, utf8_decode('p. ' . $this->PageNo()), 0, 0, 'R'); + } + } + } + + $pdf = new BibDistPrintPdf('P', 'mm', 'letter'); + $pdf->footerText = $strFooter; + $pdf->SetMargins(10, 12, 10); + $pdf->SetAutoPageBreak(true, 14); + $pdf->AddPage(); + $pdf->SetFont('Arial', 'B', 14); + $pdf->Cell(0, 8, utf8_decode(fxBibDistPrintTruncate($strEventTitle, 80)), 0, 1, 'C'); + $pdf->SetFont('Arial', '', 10); + $pdf->Cell(0, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_title', 0)), 0, 1, 'C'); + $pdf->Ln(4); + + $fltPageW = $pdf->GetPageWidth() - 20; + $fltFixedW = 18 + 42 + 42 + 12; + + foreach ($tabPanel['epreuves'] as $tabEpr) { + $epr_id = (int)($tabEpr['epr_id'] ?? 0); + if ($epr_id <= 0) { + continue; + } + + $tabEprRow = $objDatabase->fxGetRow("SELECT * FROM inscriptions_epreuves WHERE epr_id = $epr_id LIMIT 1"); + if (!is_array($tabEprRow)) { + continue; + } + + list($strSort1, $strSort2) = fxBibGetEpreuveDefaultSortKeys($tabEprRow); + $tabSelectedQue = $tabEpr['selected'] ?? []; + $tabQueCols = []; + foreach ($tabEpr['questions'] as $opt) { + if (preg_match('/^que:(\d+)$/', (string)($opt['key'] ?? ''), $m)) { + $intQueId = (int)$m[1]; + if (in_array($intQueId, $tabSelectedQue, true)) { + $tabQueCols[] = [ + 'que_id' => $intQueId, + 'label' => (string)($opt['label'] ?? ('Q' . $intQueId)), + ]; + } + } + } + + $intNbQ = count($tabQueCols); + $fltQTotal = max(0, $fltPageW - $fltFixedW); + $fltQEach = ($intNbQ > 0) ? ($fltQTotal / $intNbQ) : 0; + + $pdf->SetFont('Arial', 'B', 11); + $pdf->Cell(0, 7, utf8_decode(fxBibDistPrintTruncate($tabEpr['epr_label'] ?? '', 90)), 0, 1, 'L'); + $pdf->Ln(1); + + $tabParticipants = fxBibGetParticipantsForDistPrint($epr_id, $strSort1, $strSort2, $strLangue); + if (empty($tabParticipants)) { + $pdf->SetFont('Arial', 'I', 9); + $pdf->Cell(0, 5, utf8_decode(fxBibTexte('bib_v4_dist_print_empty_epr', 0)), 0, 1, 'L'); + $pdf->Ln(3); + continue; + } + + $tabAnswers = fxBibGetDistPrintQuestionAnswers($epr_id, $tabSelectedQue, $tabParticipants, $strLangue); + + $pdf->SetFont('Arial', 'B', 8); + $pdf->SetFillColor(230, 230, 230); + $pdf->Cell(18, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_col_bib', 0)), 1, 0, 'C', true); + $pdf->Cell(42, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_col_nom', 0)), 1, 0, 'L', true); + $pdf->Cell(42, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_col_prenom', 0)), 1, 0, 'L', true); + $pdf->Cell(12, 6, utf8_decode(fxBibTexte('bib_v4_dist_print_col_sexe', 0)), 1, 0, 'C', true); + foreach ($tabQueCols as $i => $col) { + $blnLast = ($i === $intNbQ - 1); + $pdf->Cell($fltQEach, 6, utf8_decode(fxBibDistPrintTruncate($col['label'], 22)), 1, $blnLast ? 1 : 0, 'L', true); + } + if ($intNbQ === 0) { + $pdf->Ln(); + } + + $pdf->SetFont('Arial', '', 8); + foreach ($tabParticipants as $row) { + $strKey = (int)($row['par_id'] ?? 0) . ':' . (int)($row['pec_id'] ?? 0); + $pdf->Cell(18, 5, utf8_decode((string)(int)($row['no_bib'] ?? 0)), 1, 0, 'C'); + $pdf->Cell(42, 5, utf8_decode(fxBibDistPrintTruncate($row['par_nom'] ?? '', 24)), 1, 0, 'L'); + $pdf->Cell(42, 5, utf8_decode(fxBibDistPrintTruncate($row['par_prenom'] ?? '', 24)), 1, 0, 'L'); + $pdf->Cell(12, 5, utf8_decode(fxBibDistPrintSexeLabel($row['par_sexe'] ?? '', $strLangue)), 1, 0, 'C'); + foreach ($tabQueCols as $i => $col) { + $intQueId = (int)$col['que_id']; + $strAns = $tabAnswers[$strKey][$intQueId] ?? ''; + $blnLast = ($i === $intNbQ - 1); + $pdf->Cell($fltQEach, 5, utf8_decode(fxBibDistPrintTruncate($strAns, 24)), 1, $blnLast ? 1 : 0, 'L'); + } + if ($intNbQ === 0) { + $pdf->Ln(); + } + } + + $pdf->Ln(5); + } + + $strFilename = 'distribution-dossards-' . $int_eve_id . '.pdf'; + $pdf->Output('I', $strFilename); +} + +/** MSIN-4433 — Coquille panneau impression (chargement AJAX). */ +function renderBibDistPrintPanelShell($int_eve_id, $strLangue = 'fr') { + global $vDomaine; + + $int_eve_id = (int)$int_eve_id; + $strWait = fxBibTexte('bib_v4_js_loader_wait', 0); + if ($strWait === '' || $strWait === 'bib_v4_js_loader_wait') { + $strWait = ($strLangue === 'en') ? 'Loading...' : 'Chargement...'; + } + $strRunnerSrc = $vDomaine . '/images/ms1-runner-loader.gif?v=' . _VERSION_CODE; + + ob_start(); + ?> + + + +