Définissez des séquences de dossards et assignez les numéros aux participants inscrits.
Workflow typique
- Créer une ou plusieurs séquences (ex. 1–500).
- Choisir le mode batch ou l''assignation automatique.
- Utiliser Simuler avant GO.
diff --git a/css/ms1-doc.css b/css/ms1-doc.css new file mode 100644 index 0000000..53e4c3c --- /dev/null +++ b/css/ms1-doc.css @@ -0,0 +1,300 @@ +body.ms1-doc-open { + overflow: hidden; +} + + * Distinct de ms1-bib / Tippy (info_aide). + */ + +/* Bouton ? à côté d'un titre de bloc */ +.ms1-doc-trigger, +.btn-doc-trigger { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + margin-left: 6px; + padding: 0; + border: 1px solid #6c757d; + border-radius: 50%; + background: #6c757d; + color: #fff; + font-size: 12px; + font-weight: 600; + line-height: 1; + vertical-align: middle; + cursor: pointer; +} + +.ms1-doc-trigger:hover, +.btn-doc-trigger:hover { + background: #5a6268; + border-color: #5a6268; + color: #fff; +} + +/* Overlay plein écran */ +.ms1-doc-overlay { + position: fixed; + inset: 0; + z-index: 10050; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; +} + +.ms1-doc-overlay[hidden] { + display: none !important; +} + +.ms1-doc-backdrop { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.55); + cursor: pointer; +} + +/* Panneau central */ +.ms1-doc-panel { + position: relative; + z-index: 1; + width: 100%; + max-width: 560px; + max-height: 90vh; + display: flex; + flex-direction: column; + background: #fff; + border-radius: 10px; + border: 1px solid #dee2e6; + overflow: hidden; +} + +.ms1-doc-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + padding: 14px 16px; + border-bottom: 1px solid #dee2e6; + background: #f8f9fa; +} + +.ms1-doc-title { + margin: 0; + font-size: 1.1rem; + font-weight: 600; + color: #212529; + line-height: 1.3; +} + +.ms1-doc-subtitle { + margin: 4px 0 0; + font-size: 0.85rem; + color: #6c757d; + line-height: 1.35; +} + +.ms1-doc-close { + flex-shrink: 0; + width: 32px; + height: 32px; + padding: 0; + border: none; + border-radius: 4px; + background: transparent; + color: #6c757d; + font-size: 24px; + line-height: 1; + cursor: pointer; +} + +.ms1-doc-close:hover { + background: #e9ecef; + color: #212529; +} + +.ms1-doc-body { + flex: 1; + overflow: auto; + padding: 16px 18px; + min-height: 120px; +} + +.ms1-doc-page[hidden] { + display: none !important; +} + +/* Typographie contenu HTML admin */ +.ms1-doc-format, +.ms1-doc-content { + font-size: 0.9rem; + line-height: 1.55; + color: #212529; +} + +.ms1-doc-format h2, +.ms1-doc-format h3, +.ms1-doc-content h2, +.ms1-doc-content h3 { + margin: 14px 0 8px; + font-size: 0.95rem; + font-weight: 600; + color: #212529; +} + +.ms1-doc-format h2:first-child, +.ms1-doc-format h3:first-child, +.ms1-doc-content h2:first-child, +.ms1-doc-content h3:first-child { + margin-top: 0; +} + +.ms1-doc-format p, +.ms1-doc-content p { + margin: 0 0 10px; +} + +.ms1-doc-format ol, +.ms1-doc-format ul, +.ms1-doc-content ol, +.ms1-doc-content ul { + margin: 0 0 10px; + padding-left: 1.35rem; +} + +.ms1-doc-format li, +.ms1-doc-content li { + margin-bottom: 4px; +} + +.ms1-doc-format blockquote, +.ms1-doc-content blockquote { + margin: 12px 0; + padding: 10px 12px; + border-left: 3px solid #007bff; + background: #f8f9fa; + font-size: 0.85rem; +} + +.ms1-doc-format table, +.ms1-doc-content table { + width: 100%; + border-collapse: collapse; + margin-bottom: 10px; + font-size: 0.85rem; +} + +.ms1-doc-format th, +.ms1-doc-format td, +.ms1-doc-content th, +.ms1-doc-content td { + border: 1px solid #dee2e6; + padding: 6px 8px; + text-align: left; + vertical-align: top; +} + +.ms1-doc-format th, +.ms1-doc-content th { + background: #f1f3f5; + font-weight: 600; +} + +.ms1-doc-format strong, +.ms1-doc-content strong { + font-weight: 600; +} + +/* Pied — navigation */ +.ms1-doc-footer { + padding: 10px 14px 12px; + border-top: 1px solid #dee2e6; + background: #f8f9fa; +} + +.ms1-doc-nav-dots { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; + margin-bottom: 8px; +} + +.ms1-doc-dot { + width: 36px; + height: 36px; + padding: 0; + border: 2px solid #dee2e6; + border-radius: 6px; + background: #fff; + color: #495057; + font-size: 13px; + font-weight: 600; + cursor: pointer; +} + +.ms1-doc-dot.is-active, +.ms1-doc-dot:hover { + border-color: #007bff; + background: #e7f1ff; + color: #0056b3; +} + +.ms1-doc-nav-arrows { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; +} + +.ms1-doc-prev, +.ms1-doc-next { + padding: 4px 12px; + border: 1px solid #6c757d; + border-radius: 4px; + background: #fff; + color: #495057; + font-size: 0.8rem; + cursor: pointer; +} + +.ms1-doc-prev:hover:not(:disabled), +.ms1-doc-next:hover:not(:disabled) { + background: #e9ecef; +} + +.ms1-doc-prev:disabled, +.ms1-doc-next:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.ms1-doc-counter { + font-size: 0.8rem; + color: #6c757d; + min-width: 48px; + text-align: center; +} + +/* Métadonnées JSON — jamais affichées */ +script.ms1-doc-meta { + display: none !important; +} + +@media (max-width: 576px) { + .ms1-doc-overlay { + padding: 8px; + } + + .ms1-doc-panel { + max-height: 95vh; + border-radius: 8px; + } + + .ms1-doc-dot { + width: 32px; + height: 32px; + font-size: 12px; + } +} diff --git a/js/ms1-doc.js b/js/ms1-doc.js new file mode 100644 index 0000000..53fe2f4 --- /dev/null +++ b/js/ms1-doc.js @@ -0,0 +1,163 @@ +/** + * MS1 — Module documentation overlay (navigation + ouverture/fermeture). + */ +(function () { + 'use strict'; + + function findOverlayForTrigger(trigger) { + var anchor = trigger.getAttribute('data-doc-anchor'); + if (!anchor) { + return null; + } + var overlays = document.querySelectorAll('.ms1-doc-overlay[data-doc-anchor="' + anchor + '"]'); + return overlays.length ? overlays[0] : null; + } + + function getMeta(overlay) { + var anchor = overlay.getAttribute('data-doc-anchor'); + var script = document.querySelector('script.ms1-doc-meta[data-doc-anchor="' + anchor + '"]'); + if (!script) { + return []; + } + try { + return JSON.parse(script.textContent || '[]'); + } catch (e) { + return []; + } + } + + function setPage(overlay, index) { + var pages = overlay.querySelectorAll('.ms1-doc-page'); + var dots = overlay.querySelectorAll('.ms1-doc-dot'); + var meta = getMeta(overlay); + var total = pages.length; + + if (total === 0) { + return; + } + + if (index < 0) { + index = 0; + } + if (index >= total) { + index = total - 1; + } + + pages.forEach(function (page, i) { + if (i === index) { + page.removeAttribute('hidden'); + } else { + page.setAttribute('hidden', 'hidden'); + } + }); + + dots.forEach(function (dot, i) { + if (i === index) { + dot.classList.add('is-active'); + } else { + dot.classList.remove('is-active'); + } + }); + + var titleEl = overlay.querySelector('.ms1-doc-title'); + var subtitleEl = overlay.querySelector('.ms1-doc-subtitle'); + var currentEl = overlay.querySelector('.ms1-doc-counter-current'); + var prevBtn = overlay.querySelector('.ms1-doc-prev'); + var nextBtn = overlay.querySelector('.ms1-doc-next'); + + if (meta[index]) { + if (titleEl) { + titleEl.textContent = meta[index].titre || ''; + } + if (subtitleEl) { + subtitleEl.textContent = meta[index].sous_titre || ''; + } + } + + if (currentEl) { + currentEl.textContent = String(index + 1); + } + + if (prevBtn) { + prevBtn.disabled = index <= 0; + } + if (nextBtn) { + nextBtn.disabled = index >= total - 1; + } + + overlay.setAttribute('data-doc-current-index', String(index)); + } + + function openOverlay(overlay) { + overlay.removeAttribute('hidden'); + document.body.classList.add('ms1-doc-open'); + var idx = parseInt(overlay.getAttribute('data-doc-current-index') || '0', 10); + setPage(overlay, idx); + } + + function closeOverlay(overlay) { + overlay.setAttribute('hidden', 'hidden'); + if (!document.querySelector('.ms1-doc-overlay:not([hidden])')) { + document.body.classList.remove('ms1-doc-open'); + } + } + + function getCurrentIndex(overlay) { + return parseInt(overlay.getAttribute('data-doc-current-index') || '0', 10); + } + + document.addEventListener('click', function (e) { + var trigger = e.target.closest('.ms1-doc-trigger, .btn-doc-trigger'); + if (trigger) { + e.preventDefault(); + var overlay = findOverlayForTrigger(trigger); + if (overlay) { + openOverlay(overlay); + } + return; + } + + var closeEl = e.target.closest('[data-ms1-doc-close]'); + if (closeEl) { + var overlayClose = closeEl.closest('.ms1-doc-overlay'); + if (overlayClose) { + closeOverlay(overlayClose); + } + return; + } + + var dot = e.target.closest('.ms1-doc-dot'); + if (dot) { + var overlayDot = dot.closest('.ms1-doc-overlay'); + if (overlayDot) { + setPage(overlayDot, parseInt(dot.getAttribute('data-doc-page-index') || '0', 10)); + } + return; + } + + if (e.target.closest('[data-ms1-doc-prev]')) { + var overlayPrev = e.target.closest('.ms1-doc-overlay'); + if (overlayPrev) { + setPage(overlayPrev, getCurrentIndex(overlayPrev) - 1); + } + return; + } + + if (e.target.closest('[data-ms1-doc-next]')) { + var overlayNext = e.target.closest('.ms1-doc-overlay'); + if (overlayNext) { + setPage(overlayNext, getCurrentIndex(overlayNext) + 1); + } + } + }); + + document.addEventListener('keydown', function (e) { + if (e.key !== 'Escape') { + return; + } + var open = document.querySelector('.ms1-doc-overlay:not([hidden])'); + if (open) { + closeOverlay(open); + } + }); +})(); diff --git a/min/groupsConfig.php b/min/groupsConfig.php index 2d14699..9a0e88c 100644 --- a/min/groupsConfig.php +++ b/min/groupsConfig.php @@ -25,7 +25,8 @@ return array( '//plugins/kartik-v-bootstrap-popover-x-36c3827/css/bootstrap-popover-x.min.css', '//plugins/select2-4.0.13/dist/css/select2.min.css', '//plugins/select2-bootstrap4-theme-1.5.2/dist/select2-bootstrap4.min.css', - '//css/style.css' + '//css/style.css', + '//css/ms1-doc.css' ), 'js' => array( '//libs/jquery/jquery-3.4.1.min.js', @@ -43,6 +44,7 @@ return array( '//plugins/flatpickr-4.6.13/dist/flatpickr.min.js', '//plugins/bootbox-5.4/bootbox.min.js', '//plugins/select2-4.0.13/dist/js/select2.full.min.js', - '//plugins/kartik-v-bootstrap-popover-x-36c3827/js/bootstrap-popover-x.min.js' + '//plugins/kartik-v-bootstrap-popover-x-36c3827/js/bootstrap-popover-x.min.js', + '//js/ms1-doc.js' ) ); \ No newline at end of file diff --git a/php/inc_fonctions.php b/php/inc_fonctions.php index 2ccde65..7a2b08e 100644 --- a/php/inc_fonctions.php +++ b/php/inc_fonctions.php @@ -5,6 +5,7 @@ define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/..') . '/'); // initialiser la variable $binEnvironementDev en utiliant le nom de domaine include_once APPLICATION_PATH . 'php/inc_fonctions_sl.php'; +include_once APPLICATION_PATH . 'php/inc_fx_doc.php'; include_once APPLICATION_PATH . 'php/inc_settings.php'; include_once APPLICATION_PATH . 'php/inc_mysql.php'; include_once APPLICATION_PATH . 'php/inc_dates.php'; diff --git a/php/inc_fx_doc.php b/php/inc_fx_doc.php new file mode 100644 index 0000000..5ffdaae --- /dev/null +++ b/php/inc_fx_doc.php @@ -0,0 +1,465 @@ +fxEscape($langue) . "' + AND doc_prg IN ('" . $objDatabase->fxEscape($page) . "', 'global') + ORDER BY doc_tri, pk_doc_page"; + $rows = $objDatabase->fxGetResults($sql); + + if (!is_array($rows)) { + return $vdoc_page; + } + + for ($i = 1; $i <= count($rows); $i++) { + if (!empty($rows[$i]['doc_page_clef'])) { + $vdoc_page[$rows[$i]['doc_page_clef']] = $rows[$i]; + } + } + + return $vdoc_page; +} + +/** Ligne doc_anchor par clé d'appel. */ +function fxDocGetAnchorRow($clef, $mem_global = 0) { + global $objDatabase; + + static $anchorCache = array(); + + $strPrg = fxDocPrg($mem_global); + $cacheKey = $clef . '|' . $strPrg; + + if (array_key_exists($cacheKey, $anchorCache)) { + return $anchorCache[$cacheKey]; + } + + if (!isset($objDatabase) || $clef === '') { + $anchorCache[$cacheKey] = array(); + return $anchorCache[$cacheKey]; + } + + $sql = "SELECT * + FROM doc_anchor + WHERE doc_actif = 1 + AND doc_anchor_clef = '" . $objDatabase->fxEscape($clef) . "' + AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global') + ORDER BY FIELD(doc_prg, '" . $objDatabase->fxEscape($strPrg) . "', 'global') + LIMIT 1"; + $row = $objDatabase->fxGetRow($sql); + $anchorCache[$cacheKey] = is_array($row) ? $row : array(); + + return $anchorCache[$cacheKey]; +} + +/** Résout doc_mod_clef depuis une clé d'ancre ou un code module direct. */ +function fxDocResolveModClef($clef, $mem_global = 0) { + $anchor = fxDocGetAnchorRow($clef, $mem_global); + + if (!empty($anchor['doc_mod_clef'])) { + return $anchor['doc_mod_clef']; + } + + return $clef; +} + +/** Pages actives d'un module, triées (pour carrousel overlay). */ +function fxDocGetModulePages($modClef, $langue = null, $mem_global = 0) { + global $objDatabase, $vdoc_page; + + if ($langue === null || $langue === '') { + $langue = fxDocLangue(); + } + + $strPrg = fxDocPrg($mem_global); + $modClef = fxDocResolveModClef($modClef, $mem_global); + + if (!isset($objDatabase) || $modClef === '') { + return array(); + } + + $sql = "SELECT * + FROM doc_page + WHERE doc_actif = 1 + AND doc_mod_clef = '" . $objDatabase->fxEscape($modClef) . "' + AND doc_langue = '" . $objDatabase->fxEscape($langue) . "' + AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global') + ORDER BY doc_tri, pk_doc_page"; + $rows = $objDatabase->fxGetResults($sql); + + $pages = array(); + if (!is_array($rows)) { + return $pages; + } + + for ($i = 1; $i <= count($rows); $i++) { + $pages[] = $rows[$i]; + if (!empty($rows[$i]['doc_page_clef'])) { + if (!isset($vdoc_page) || !is_array($vdoc_page)) { + $vdoc_page = array(); + } + $vdoc_page[$rows[$i]['doc_page_clef']] = $rows[$i]; + } + } + + return $pages; +} + +/** Une page par doc_page_clef (mémoire ou BD). */ +function fxDocGetPageRow($clef, $mem_global = 0) { + global $vdoc_page, $objDatabase; + + if (!empty($vdoc_page[$clef])) { + return $vdoc_page[$clef]; + } + + if (!isset($objDatabase) || $clef === '') { + return array(); + } + + $strPrg = fxDocPrg($mem_global); + $langue = fxDocLangue(); + + static $pageCache = array(); + $cacheKey = $clef . '|' . $langue . '|' . $strPrg; + + if (array_key_exists($cacheKey, $pageCache)) { + return $pageCache[$cacheKey]; + } + + $sql = "SELECT * + FROM doc_page + WHERE doc_actif = 1 + AND doc_page_clef = '" . $objDatabase->fxEscape($clef) . "' + AND doc_langue = '" . $objDatabase->fxEscape($langue) . "' + AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global') + ORDER BY FIELD(doc_prg, '" . $objDatabase->fxEscape($strPrg) . "', 'global') + LIMIT 1"; + $row = $objDatabase->fxGetRow($sql); + $pageCache[$cacheKey] = is_array($row) ? $row : array(); + + return $pageCache[$cacheKey]; +} + +/** Crée une page doc vide si absente (miroir afficheTexte). */ +function fxDocEnsurePage($clef, $mem_global = 0, $modClef = '') { + global $objDatabase; + + $row = fxDocGetPageRow($clef, $mem_global); + if (!empty($row)) { + return $row; + } + + $strPrg = fxDocPrg($mem_global); + $langue = fxDocLangue(); + + if ($modClef === '') { + $modClef = $clef; + } + + $tabData = array( + 'doc_mod_clef' => $modClef, + 'doc_page_clef' => $clef, + 'doc_langue' => $langue, + 'doc_titre' => $clef, + 'doc_sous_titre' => '', + 'doc_html' => '', + 'doc_prg' => $strPrg, + 'doc_tri' => 0, + 'doc_actif' => 1, + ); + fxSetDocPage('add', $tabData); + + if ($langue === 'fr') { + $tabDataEn = $tabData; + $tabDataEn['doc_langue'] = 'en'; + fxSetDocPage('add', $tabDataEn); + } + + return fxDocGetPageRow($clef, $mem_global); +} + +/** + * Titre doc_titre — équivalent afficheTexte pour doc_page. + */ +function afficheDocTitre($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') { + $row = fxDocGetPageRow($clef, $mem_global); + + if (empty($row)) { + fxDocEnsurePage($clef, $mem_global, $modClef); + $row = fxDocGetPageRow($clef, $mem_global); + } + + $str = trim($row['doc_titre'] ?? ''); + if ($str === '' || $str === $clef) { + $str = '*' . $clef . '*'; + } + + if ($mem_echo == 1) { + echo fxDocEsc($str); + } + + return $str; +} + +/** + * Corps HTML doc_html — équivalent afficheAide (contenu long, HTML admin). + */ +function afficheDocHtml($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') { + $row = fxDocGetPageRow($clef, $mem_global); + + if (empty($row)) { + fxDocEnsurePage($clef, $mem_global, $modClef); + $row = fxDocGetPageRow($clef, $mem_global); + } + + $str = $row['doc_html'] ?? ''; + + if ($mem_echo == 1) { + echo $str; + } + + return $str; +} + +/** Sous-titre doc_sous_titre. */ +function afficheDocSousTitre($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') { + $row = fxDocGetPageRow($clef, $mem_global); + + if (empty($row)) { + fxDocEnsurePage($clef, $mem_global, $modClef); + $row = fxDocGetPageRow($clef, $mem_global); + } + + $str = trim($row['doc_sous_titre'] ?? ''); + + if ($mem_echo == 1) { + echo fxDocEsc($str); + } + + return $str; +} + +/** Bouton ? pour ouvrir le module doc (clé doc_anchor_clef). */ +function fxDocRenderTrigger($anchorClef, $mem_global = 0, $strAriaLabel = 'Documentation') { + $modClef = fxDocResolveModClef($anchorClef, $mem_global); + + return ''; +} + +/** Panneau overlay paginé pour un module (clé doc_anchor_clef ou doc_mod_clef). */ +function fxDocRenderModule($anchorClef, $mem_global = 0) { + $modClef = fxDocResolveModClef($anchorClef, $mem_global); + $pages = fxDocGetModulePages($modClef, null, $mem_global); + + if (empty($pages)) { + return ''; + } + + $overlayId = 'ms1-doc-overlay-' . preg_replace('/[^a-z0-9_-]/i', '-', $anchorClef); + + $html = '
'; + + // Métadonnées titres pour JS (init header sans parser le HTML) + $html .= ''; + + return $html; +} + +/** Trigger + overlay (appel typique à côté d'un titre de bloc). */ +function fxDocRenderBlock($anchorClef, $mem_global = 0, $strAriaLabel = 'Documentation') { + return fxDocRenderTrigger($anchorClef, $mem_global, $strAriaLabel) . fxDocRenderModule($anchorClef, $mem_global); +} + +function fxSetDocMod($strAction, $tabData, $id = 0) { + global $objDatabase; + + switch ($strAction) { + case 'add': + $tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_mod', 'doc_mod', $GLOBALS['arrConDatabaseMain']['db']); + $sqlValide = "SELECT COUNT(doc_mod_clef) FROM doc_mod + WHERE doc_mod_clef = '" . $objDatabase->fxEscape($tabData['doc_mod_clef']) . "' + AND doc_mod_prg = '" . $objDatabase->fxEscape($tabData['doc_mod_prg']) . "'"; + if ((int)$objDatabase->fxGetVar($sqlValide) > 0) { + break; + } + $sqlInsert = "INSERT INTO doc_mod " . $tabFieldsValues['set'] . ', doc_mod_creation = NOW()'; + $objDatabase->fxQuery($sqlInsert); + break; + + case 'mod': + $tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_mod', 'doc_mod', $GLOBALS['arrConDatabaseMain']['db']); + $sqlUpdate = "UPDATE doc_mod " . $tabFieldsValues['set'] . ", doc_mod_maj = NOW() WHERE " . $tabFieldsValues['key']; + $objDatabase->fxQuery($sqlUpdate); + break; + } +} + +function fxSetDocPage($strAction, $tabData, $id = 0) { + global $objDatabase; + + switch ($strAction) { + case 'add': + $tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_page', 'doc_page', $GLOBALS['arrConDatabaseMain']['db']); + $sqlValide = "SELECT COUNT(doc_page_clef) FROM doc_page + WHERE doc_page_clef = '" . $objDatabase->fxEscape($tabData['doc_page_clef']) . "' + AND doc_langue = '" . $objDatabase->fxEscape($tabData['doc_langue']) . "' + AND doc_prg = '" . $objDatabase->fxEscape($tabData['doc_prg']) . "'"; + if ((int)$objDatabase->fxGetVar($sqlValide) > 0) { + break; + } + $sqlInsert = "INSERT INTO doc_page " . $tabFieldsValues['set'] . ', doc_creation = NOW()'; + $objDatabase->fxQuery($sqlInsert); + break; + + case 'mod': + $tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_page', 'doc_page', $GLOBALS['arrConDatabaseMain']['db']); + $sqlUpdate = "UPDATE doc_page " . $tabFieldsValues['set'] . ", doc_maj = NOW() WHERE " . $tabFieldsValues['key']; + $objDatabase->fxQuery($sqlUpdate); + break; + } +} + +function fxSetDocAnchor($strAction, $tabData, $id = 0) { + global $objDatabase; + + switch ($strAction) { + case 'add': + $tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_anchor', 'doc_anchor', $GLOBALS['arrConDatabaseMain']['db']); + $sqlValide = "SELECT COUNT(doc_anchor_clef) FROM doc_anchor + WHERE doc_anchor_clef = '" . $objDatabase->fxEscape($tabData['doc_anchor_clef']) . "' + AND doc_prg = '" . $objDatabase->fxEscape($tabData['doc_prg']) . "'"; + if ((int)$objDatabase->fxGetVar($sqlValide) > 0) { + break; + } + $sqlInsert = "INSERT INTO doc_anchor " . $tabFieldsValues['set'] . ', doc_creation = NOW()'; + $objDatabase->fxQuery($sqlInsert); + break; + + case 'mod': + $tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_anchor', 'doc_anchor', $GLOBALS['arrConDatabaseMain']['db']); + $sqlUpdate = "UPDATE doc_anchor " . $tabFieldsValues['set'] . " WHERE " . $tabFieldsValues['key']; + $objDatabase->fxQuery($sqlUpdate); + break; + } +} diff --git a/sql/MSIN-doc-module.sql b/sql/MSIN-doc-module.sql new file mode 100644 index 0000000..84b3465 --- /dev/null +++ b/sql/MSIN-doc-module.sql @@ -0,0 +1,111 @@ +-- Module documentation MS1 (overlay paginé) — distinct de la table info / info_aide. +-- Terminologie : doc_mod = module, doc_page = page HTML, doc_anchor = clé d'appel. +-- Exécuter sur la BD client (inscription). + +-- Réimport propre (optionnel) +-- DELETE FROM doc_page WHERE doc_mod_clef = 'bib_assignment'; +-- DELETE FROM doc_anchor WHERE doc_mod_clef = 'bib_assignment'; +-- DELETE FROM doc_mod WHERE doc_mod_clef = 'bib_assignment'; + +-- --------------------------------------------------------------------------- +-- doc_mod — registre des modules de documentation +-- --------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `doc_mod` ( + `pk_doc_mod` int(10) unsigned NOT NULL AUTO_INCREMENT, + `doc_mod_clef` varchar(45) NOT NULL, + `doc_mod_prg` varchar(100) NOT NULL DEFAULT 'global', + `doc_mod_titre` varchar(255) NOT NULL DEFAULT '', + `doc_mod_actif` tinyint(1) NOT NULL DEFAULT '1', + `doc_mod_tri` int(10) unsigned NOT NULL DEFAULT '0', + `doc_mod_creation` datetime DEFAULT NULL, + `doc_mod_maj` datetime DEFAULT NULL, + PRIMARY KEY (`pk_doc_mod`), + UNIQUE KEY `uk_doc_mod` (`doc_mod_clef`,`doc_mod_prg`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- --------------------------------------------------------------------------- +-- doc_page — pages HTML (équivalent info_texte + info_aide, mais long / riche) +-- --------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `doc_page` ( + `pk_doc_page` int(10) unsigned NOT NULL AUTO_INCREMENT, + `doc_mod_clef` varchar(45) NOT NULL, + `doc_page_clef` varchar(45) NOT NULL, + `doc_langue` varchar(2) NOT NULL DEFAULT 'fr', + `doc_titre` varchar(255) NOT NULL DEFAULT '', + `doc_sous_titre` varchar(255) NOT NULL DEFAULT '', + `doc_html` text NOT NULL, + `doc_prg` varchar(100) NOT NULL DEFAULT 'global', + `doc_tri` int(10) unsigned NOT NULL DEFAULT '0', + `doc_actif` tinyint(1) NOT NULL DEFAULT '1', + `doc_creation` datetime DEFAULT NULL, + `doc_maj` datetime DEFAULT NULL, + PRIMARY KEY (`pk_doc_page`), + UNIQUE KEY `uk_doc_page` (`doc_page_clef`,`doc_langue`,`doc_prg`), + KEY `idx_doc_mod` (`doc_mod_clef`,`doc_langue`,`doc_prg`,`doc_tri`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- --------------------------------------------------------------------------- +-- doc_anchor — clé d'appel (comme info_clef) → module à ouvrir +-- --------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `doc_anchor` ( + `pk_doc_anchor` int(10) unsigned NOT NULL AUTO_INCREMENT, + `doc_anchor_clef` varchar(45) NOT NULL, + `doc_mod_clef` varchar(45) NOT NULL, + `doc_prg` varchar(100) NOT NULL DEFAULT 'global', + `doc_actif` tinyint(1) NOT NULL DEFAULT '1', + `doc_creation` datetime DEFAULT NULL, + PRIMARY KEY (`pk_doc_anchor`), + UNIQUE KEY `uk_doc_anchor` (`doc_anchor_clef`,`doc_prg`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- --------------------------------------------------------------------------- +-- Données initiales — assignation de dossard (bib_assignment) +-- --------------------------------------------------------------------------- + +INSERT INTO `doc_mod` (`doc_mod_clef`, `doc_mod_prg`, `doc_mod_titre`, `doc_mod_actif`, `doc_mod_tri`, `doc_mod_creation`) +VALUES ('bib_assignment', 'compte.php', 'Assignation de dossard', 1, 10, NOW()); + +INSERT INTO `doc_anchor` (`doc_anchor_clef`, `doc_mod_clef`, `doc_prg`, `doc_actif`, `doc_creation`) +VALUES ('bib_v4_assign_doc', 'bib_assignment', 'compte.php', 1, NOW()); + +-- FR — pages du guide (contenu à composer ; structure HTML prête) +INSERT INTO `doc_page` (`doc_mod_clef`, `doc_page_clef`, `doc_langue`, `doc_titre`, `doc_sous_titre`, `doc_html`, `doc_prg`, `doc_tri`, `doc_actif`, `doc_creation`) VALUES +('bib_assignment', 'bib_doc_overview', 'fr', 'Assignation de dossard', 'Vue d''ensemble du module', +'Définissez des séquences de dossards et assignez les numéros aux participants inscrits.
Début / Fin — bornes de la séquence. OK enregistre. Suppr. retire la séquence.
Statistiques : Dernier, Utilisé, Dispo.
Ajoute une nouvelle séquence pour cette épreuve.
Cochez les séquences, choisissez l''ordre d''assignation, puis Simuler (aperçu) ou GO (exécution).
Réinitialiser retire les dossards déjà assignés — à utiliser avec prudence.
Attribue un dossard à chaque nouvelle inscription dans les séquences cochées.
Accepter enregistre la configuration. Le badge Auto actif confirme que le mode est en marche.
Chaque participant reçoit son propre numéro. L''option « Nom d''équipe » est masquée sur ce type d''épreuve.
Choisissez la méthode d''assignation avant le premier dossard — le choix est verrouillé ensuite.
Define bib sequences and assign numbers to registered participants.
Start / End define the sequence range.
Select sequences, choose sort order, then Simulate or GO.
Assigns a bib automatically for each new registration.
Each participant gets their own bib number.
Choose assignment method before the first bib is assigned.