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.

Workflow typique

  1. Créer une ou plusieurs séquences (ex. 1–500).
  2. Choisir le mode batch ou l''assignation automatique.
  3. Utiliser Simuler avant GO.
', +'compte.php', 10, 1, NOW()), +('bib_assignment', 'bib_doc_sequences', 'fr', 'Séquences de dossards', 'Colonnes et actions du tableau', +'

Colonnes

Début / Fin — bornes de la séquence. OK enregistre. Suppr. retire la séquence.

Statistiques : Dernier, Utilisé, Dispo.

+ Ajouter

Ajoute une nouvelle séquence pour cette épreuve.

', +'compte.php', 20, 1, NOW()), +('bib_assignment', 'bib_doc_batch', 'fr', 'Assigner par séquence', 'Mode batch — Simuler puis GO', +'

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.

', +'compte.php', 30, 1, NOW()), +('bib_assignment', 'bib_doc_auto', 'fr', 'Assignation automatique', 'Dossard à chaque inscription', +'

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.

', +'compte.php', 40, 1, NOW()), +('bib_assignment', 'bib_doc_solo', 'fr', 'Épreuve individuelle', 'Participants solo', +'

Chaque participant reçoit son propre numéro. L''option « Nom d''équipe » est masquée sur ce type d''épreuve.

', +'compte.php', 50, 1, NOW()), +('bib_assignment', 'bib_doc_equipe', 'fr', 'Épreuve en équipe', 'Dossards individuels vs 1 équipe = 1 dossard', +'

Choisissez la méthode d''assignation avant le premier dossard — le choix est verrouillé ensuite.

', +'compte.php', 60, 1, NOW()); + +-- EN — stubs (à traduire) +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', 'en', 'Bib assignment', 'Module overview', +'

Define bib sequences and assign numbers to registered participants.

', +'compte.php', 10, 1, NOW()), +('bib_assignment', 'bib_doc_sequences', 'en', 'Bib sequences', 'Table columns and actions', +'

Start / End define the sequence range.

', +'compte.php', 20, 1, NOW()), +('bib_assignment', 'bib_doc_batch', 'en', 'Assign by sequence', 'Batch mode', +'

Select sequences, choose sort order, then Simulate or GO.

', +'compte.php', 30, 1, NOW()), +('bib_assignment', 'bib_doc_auto', 'en', 'Automatic assignment', 'Bib on each registration', +'

Assigns a bib automatically for each new registration.

', +'compte.php', 40, 1, NOW()), +('bib_assignment', 'bib_doc_solo', 'en', 'Individual race', 'Solo participants', +'

Each participant gets their own bib number.

', +'compte.php', 50, 1, NOW()), +('bib_assignment', 'bib_doc_equipe', 'en', 'Team race', 'Individual bibs vs one bib per team', +'

Choose assignment method before the first bib is assigned.

', +'compte.php', 60, 1, NOW()); diff --git a/~$MSIN-4379-bib_v4-traductions.xlsx b/~$MSIN-4379-bib_v4-traductions.xlsx deleted file mode 100644 index 1a17c3e..0000000 Binary files a/~$MSIN-4379-bib_v4-traductions.xlsx and /dev/null differ