diff --git a/Js/fonctions.js b/Js/fonctions.js index b41045f..78a3f7d 100755 --- a/Js/fonctions.js +++ b/Js/fonctions.js @@ -1,514 +1,364 @@ +/** + * INITIALISATION GÉNÉRALE + */ +$(function() { + // 1. Initialisation des composants au chargement de la page + appliquerDataTable(); -$(function(){ - - appliquerDataTable(); + // 2. Lancement du cycle de rafraîchissement automatique + const vueActuelle = $("#vue").val(); + // On ne lance le timer que si l'utilisateur est connecté (pas sur la vue Connexion) + if (vueActuelle !== "Connexion") { + setInterval(function() { + // On vérifie si l'onglet est actif pour éviter des requêtes inutiles + if (!document.hidden) { + raffraichier_gabarit(); + } + }, 60000); // 60 secondes + } }); +/** + * Fonction principale de rafraîchissement du gabarit (UI et Session) + * Gère la mise à jour des données et la redirection forcée en cas de déconnexion. + */ +function raffraichier_gabarit() { + const racine = $("#racineWeb").val() || "/"; + const vueActuelle = $("#vue").val(); -// Gestion du menu burger - Version simplifiée -document.addEventListener('DOMContentLoaded', function() { - const burgerToggle = document.getElementById('burgerMenuToggle'); - const burgerDropdown = document.getElementById('burgerDropdown'); - - if (burgerToggle && burgerDropdown) { - // Ouvrir/fermer le menu burger - burgerToggle.addEventListener('click', function(e) { - e.preventDefault(); - e.stopPropagation(); - burgerDropdown.classList.toggle('show'); + $.ajax({ + url: racine + "Ajaxgabarit/", + type: 'GET', + cache: false, + success: function(data) { + // Injection du nouveau contenu + $("#div_ajaxgabarit").html(data); + + const codeSociete = $("#codeSociete").val(); + + // VERIFICATION DE LA SESSION + if ((!codeSociete || codeSociete.trim() === "") && vueActuelle !== "Connexion") { + + // Préparation des messages + const msg = "Votre session a expiré. Veuillez vous reconnecter."; + const msgEng = "Your session has expired. Please log in again."; + + // Utilisation de votre fonction confirm_ebene pour bloquer l'utilisateur + // On utilise confirm pour forcer l'utilisateur à cliquer sur "Oui" (OK) + confirm_ebene(msg, msgEng).then((isConfirmed) => { + // Peu importe le clic, on redirige car la session est morte + window.location.assign(racine + "Connexion/deconnecter/"); + }); + + return; + } + + // Note: On évite le Toast ici pour ne pas surcharger alert_ebene + }, + error: function(xhr) { + if (xhr.status === 401 && vueActuelle !== "Connexion") { + window.location.assign(racine + "Connexion/deconnecter/"); + } + }, + complete: function() { + $(".datepicker").datepicker(); + if (typeof raffraichier_messagerie === "function") { + raffraichier_messagerie(); + } + } }); +} + +/** + * GESTION DU MENU BURGER + * Gère l'affichage mobile et la fermeture lors d'un clic extérieur. + */ +document.addEventListener('DOMContentLoaded', () => { + const burgerToggle = document.getElementById('burgerMenuToggle'); + const burgerDropdown = document.getElementById('burgerDropdown'); - // Fermer le menu quand on clique ailleurs sur la page - document.addEventListener('click', function(e) { - if (!e.target.closest('.burger-menu-container')) { - burgerDropdown.classList.remove('show'); - } - }); - - // Empêcher la fermeture quand on clique dans le menu dropdown - burgerDropdown.addEventListener('click', function(e) { - e.stopPropagation(); - }); - } + if (burgerToggle && burgerDropdown) { + // Alterne la classe 'show' au clic sur le bouton + burgerToggle.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + burgerDropdown.classList.toggle('show'); + }); + + // Ferme le menu si on clique n'importe où ailleurs sur le document + document.addEventListener('click', (e) => { + if (!e.target.closest('.burger-menu-container')) { + burgerDropdown.classList.remove('show'); + } + }); + + // Empêche la fermeture si on clique à l'intérieur du menu lui-même + burgerDropdown.addEventListener('click', (e) => e.stopPropagation()); + } }); - -// Fonction pour formater les messages avec retours à la ligne automatiques +/** + * FORMATAGE DES MESSAGES (Responsive) + * Ajoute des balises
pour forcer le retour à la ligne selon la taille d'écran. + */ function formatMessageForSwal(message) { if (!message) return ''; - // Définir la longueur maximale par ligne selon la largeur de l'écran const screenWidth = window.innerWidth; - let maxLineLength; + // Détermine la limite de caractères avant le saut de ligne + const maxLineLength = screenWidth < 576 ? 40 : (screenWidth < 768 ? 60 : 80); - if (screenWidth < 576) { // Mobile - maxLineLength = 40; - } else if (screenWidth < 768) { // Tablet - maxLineLength = 60; - } else { // Desktop - maxLineLength = 80; - } + if (message.length <= maxLineLength && !message.includes('\n')) return message; - // Si le message est déjà court, ne pas le modifier - if (message.length <= maxLineLength && !message.includes('\n')) { - return message; - } - - // Diviser le message en mots const words = message.split(' '); let lines = []; let currentLine = ''; words.forEach(word => { - // Si ajouter ce mot dépasse la limite, créer une nouvelle ligne if ((currentLine + ' ' + word).length > maxLineLength && currentLine !== '') { lines.push(currentLine); currentLine = word; } else { - // Ajouter le mot à la ligne courante currentLine = currentLine ? currentLine + ' ' + word : word; } }); - // Ajouter la dernière ligne - if (currentLine) { - lines.push(currentLine); - } - + if (currentLine) lines.push(currentLine); return lines.join('
'); } -// Fonction pour ajuster dynamiquement le contenu SweetAlert +/** + * AJUSTEMENT DYNAMIQUE SWEETALERT + * Redimensionne la popup et gère le scroll interne pour les longs textes. + */ function adjustSwalContent() { const popup = Swal.getPopup(); const title = Swal.getTitle(); const htmlContainer = Swal.getHtmlContainer(); if (popup && title) { - // Ajuster la largeur maximale selon l'écran const screenWidth = window.innerWidth; + // Application de styles responsives directs if (screenWidth < 576) { - popup.style.maxWidth = '95vw'; - popup.style.width = '95vw'; - popup.style.margin = '10px'; - } else if (screenWidth < 768) { - popup.style.maxWidth = '85vw'; - popup.style.width = '85vw'; + Object.assign(popup.style, { maxWidth: '95vw', width: '95vw', margin: '10px' }); } else { - popup.style.maxWidth = '500px'; - popup.style.width = '500px'; + Object.assign(popup.style, { maxWidth: screenWidth < 768 ? '85vw' : '500px', width: '100%' }); } - // Gérer le défilement si nécessaire - const titleHeight = title.scrollHeight; + // Gestion du scroll si le titre est trop grand const maxTitleHeight = Math.min(window.innerHeight * 0.6, 400); - - if (titleHeight > maxTitleHeight) { - title.style.overflowY = 'auto'; - title.style.maxHeight = maxTitleHeight + 'px'; - title.style.paddingRight = '10px'; - } - - // Ajuster également le conteneur HTML si présent - if (htmlContainer) { - const containerHeight = htmlContainer.scrollHeight; - const maxContainerHeight = Math.min(window.innerHeight * 0.4, 300); - - if (containerHeight > maxContainerHeight) { - htmlContainer.style.overflowY = 'auto'; - htmlContainer.style.maxHeight = maxContainerHeight + 'px'; - htmlContainer.style.paddingRight = '10px'; - } + if (title.scrollHeight > maxTitleHeight) { + Object.assign(title.style, { overflowY: 'auto', maxHeight: maxTitleHeight + 'px', paddingRight: '10px' }); } } } -// Fonction d'alerte principale -function alert_ebene(p_msg, p_msg_eng) { - let codeLangue = $("#codeLangue").val(); - let message = (codeLangue === "en_US") ? p_msg_eng : p_msg; - - // Formater le message pour les retours à la ligne - let formattedMessage = formatMessageForSwal(message); - - Swal.fire({ - title: formattedMessage, - icon: 'info', - confirmButtonText: codeLangue === "en_US" ? 'OK' : 'D\'accord', +/** + * WRAPPER GÉNÉRIQUE SWEETALERT + * Centralise les paramètres communs pour alert_ebene, confirm_ebene, etc. + */ +function baseSwal(options) { + return Swal.fire({ + ...options, customClass: { popup: 'responsive-swal-popup', title: 'responsive-swal-title', htmlContainer: 'responsive-swal-html' }, - didOpen: () => { - adjustSwalContent(); - }, - willOpen: () => { - // Ajustement avant l'ouverture - document.body.style.overflow = 'hidden'; - }, - willClose: () => { - document.body.style.overflow = 'auto'; - } + didOpen: adjustSwalContent, + willOpen: () => { document.body.style.overflow = 'hidden'; }, + willClose: () => { document.body.style.overflow = 'auto'; } }); } -// Fonction de confirmation +/** + * ALERTE SIMPLE + * Affiche une information bilingue. + */ +function alert_ebene(p_msg, p_msg_eng) { + const codeLangue = $("#codeLangue").val(); + const message = (codeLangue === "en_US") ? p_msg_eng : p_msg; + + baseSwal({ + title: formatMessageForSwal(message), + icon: 'info', + confirmButtonText: codeLangue === "en_US" ? 'OK' : 'D\'accord' + }); +} + +/** + * CONFIRMATION + * Affiche une boîte de dialogue Oui/Non et retourne une promesse. + */ function confirm_ebene(p_msg, p_msg_eng) { - let codeLangue = $("#codeLangue").val(); - let message = (codeLangue === "en_US") ? p_msg_eng : p_msg; + const codeLangue = $("#codeLangue").val(); + const message = (codeLangue === "en_US") ? p_msg_eng : p_msg; - // Formater le message pour les retours à la ligne - let formattedMessage = formatMessageForSwal(message); - - return Swal.fire({ - title: formattedMessage, + return baseSwal({ + title: formatMessageForSwal(message), icon: 'warning', showCancelButton: true, confirmButtonText: codeLangue === "en_US" ? 'Yes' : 'Oui', - cancelButtonText: codeLangue === "en_US" ? 'No' : 'Non', - customClass: { - popup: 'responsive-swal-popup', - title: 'responsive-swal-title', - htmlContainer: 'responsive-swal-html' - }, - didOpen: () => { - adjustSwalContent(); - }, - willOpen: () => { - document.body.style.overflow = 'hidden'; - }, - willClose: () => { - document.body.style.overflow = 'auto'; - } - }).then((result) => { - return result.isConfirmed; - }); + cancelButtonText: codeLangue === "en_US" ? 'No' : 'Non' + }).then(result => result.isConfirmed); } -// Fonction de prompt +/** + * SAISIE TEXTE (PROMPT) + * Ouvre une modale avec champ de saisie et exécute un callback avec la valeur. + */ function prompt_ebene(p_msg, p_msg_eng, p_retour, callback) { - let codeLangue = $("#codeLangue").val(); - let message = (codeLangue === "en_US") ? p_msg_eng : p_msg; + const codeLangue = $("#codeLangue").val(); + const message = (codeLangue === "en_US") ? p_msg_eng : p_msg; - // Formater le message pour les retours à la ligne - let formattedMessage = formatMessageForSwal(message); - - Swal.fire({ - title: formattedMessage, + baseSwal({ + title: formatMessageForSwal(message), input: 'text', inputValue: p_retour, showCancelButton: true, confirmButtonText: 'OK', - cancelButtonText: 'Annuler', - customClass: { - popup: 'responsive-swal-popup', - title: 'responsive-swal-title', - htmlContainer: 'responsive-swal-html' - }, - didOpen: () => { - adjustSwalContent(); - }, - willOpen: () => { - document.body.style.overflow = 'hidden'; - }, - willClose: () => { - document.body.style.overflow = 'auto'; - } - }).then((result) => { - if (result.isConfirmed) { - callback(result.value); - } else { - callback(null); - } + cancelButtonText: codeLangue === "en_US" ? 'Cancel' : 'Annuler' + }).then(result => { + callback(result.isConfirmed ? result.value : null); }); } -// Écouter les changements de taille de fenêtre -window.addEventListener('resize', () => { - // Réajuster si une alerte est ouverte - if (Swal.isVisible()) { - setTimeout(adjustSwalContent, 100); - } -}); +/** + * CHANGEMENT DE MOT DE PASSE + * Demande confirmation avant redirection. + */ +function change_password() { + const v_msg = "Attention, vous serez déconnecté par la suite! Voulez-vous changer votre mot de passe?"; + const v_msgEng = "Attention, you will be logged out afterwards! Do you want to change your password?"; - -function change_password() -{ - - v_msg="Attention, vous serez déconnecté par la suite! Voulez-vous changer votre mot de passe?"; - v_msgEng="Attention, you will be logged out afterwards! Do you want to change your password?"; - - confirm_ebene(v_msg, v_msgEng) - .then((isConfirmed) => { + confirm_ebene(v_msg, v_msgEng).then(isConfirmed => { if (isConfirmed) { - // L'utilisateur a confirmé - window.location.assign($("#racineWeb" ).val()+"Changermotpass/"); - } else { - // L'utilisateur a annulé - console.log("Confirmation refusée"); + window.location.assign($("#racineWeb").val() + "Changermotpass/"); } }); -} - - -function afficher_beneficiaire_id() -{ - idBeneficiaire=$("#idBeneficiaire_C").val(); - okId=$("#okId").val(); - - // alert("okId = "+okId); - - // alert("idBeneficiaire:"+idBeneficiaire+"fin"); - //return - - $("#contenu").html('
' + '' + '
'); - - - if (idBeneficiaire>"") - { - ajax_context_beneficiaire_afficher(idBeneficiaire, okId); - } } -function afficher_beneficiaire_id_okId() -{ - idBeneficiaire=$("#idBeneficiaire_C").val(); - okId=$("#okId").val(); - - if (idBeneficiaire>"") - { - ajax_context_beneficiaire_afficher(idBeneficiaire, okId); - } -} - -function ajax_context_beneficiaire_afficher(idBeneficiaire, okId) -{ - donnees = 'idBeneficiaire='+idBeneficiaire+'&okId='+okId; - - $.ajax({ - url: $("#racineWeb").val()+"Ajaxcontextbeneficiaire/", - type : 'post', - data: donnees, - error: function(errorData) { - // alert("Erreur : "+errorData); - }, - complete: function() { - window.location.assign($("#racineWeb" ).val()+"Fichebeneficiaire/"+idBeneficiaire); - } - }); -} - -function change_password() -{ - - v_msg="Attention, vous serez déconnecté par la suite! Voulez-vous changer votre mot de passe?"; - v_msgEng="Attention, you will be logged out afterwards! Do you want to change your password?"; - - confirm_ebene(v_msg, v_msgEng) - .then((isConfirmed) => { - if (isConfirmed) { - // L'utilisateur a confirmé - window.location.assign($("#racineWeb" ).val()+"Changermotpass/"); - } else { - // L'utilisateur a annulé - console.log("Confirmation refusée"); +/** + * AFFICHAGE BÉNÉFICIAIRE + * Prépare le contexte serveur avant de charger la fiche bénéficiaire. + */ +function ajax_context_beneficiaire_afficher(idBeneficiaire, okId) { + // Affiche un loader pendant le chargement + $("#contenu").html('
'); + + $.ajax({ + url: $("#racineWeb").val() + "Ajaxcontextbeneficiaire/", + type: 'post', + data: { idBeneficiaire, okId }, + complete: () => { + window.location.assign($("#racineWeb").val() + "Fichebeneficiaire/" + idBeneficiaire); } }); -} - -function changer_langue_connexion() -{ - codeLangue = $("#langue").val(); - donnees = 'codeLangue='+codeLangue; - - $.ajax({ - url: $("#racineWeb").val()+"Ajaxconnexioncookie/changerlangue/", - type : 'post', - data: donnees, - error: function(errorData) - { - }, - success: function(data) - { - $("#div_detail_connexion").html(data); - }, - complete: function() - { - $(".selectpicker").selectpicker(); - } - }); - } -function connexion_cookie() -{ - msgErreur=$("#msgErreur").val(); - - donnees = 'msgErreur='+msgErreur; - - $.ajax({ - url: $("#racineWeb").val()+"Ajaxconnexioncookie/", - type: 'POST', - data: donnees, - success: function(data) - { - $("#div_ajaxconnexion").html(data); - }, - error: function(errorData) - { - }, - complete: function() - { - var login = document.getElementById("login").value; - if (login>" ") - { - $("#mdp").focus(); - } - else - { - $("#login").focus(); - } - } - }); +/** + * GESTION DE LA LANGUE À LA CONNEXION + * Met à jour le bloc de connexion via AJAX lors du changement de langue. + */ +function changer_langue_connexion() { + const codeLangue = $("#langue").val(); + $.ajax({ + url: $("#racineWeb").val() + "Ajaxconnexioncookie/changerlangue/", + type: 'post', + data: { codeLangue }, + success: (data) => $("#div_detail_connexion").html(data), + complete: () => $(".selectpicker").selectpicker('refresh') + }); } -// Applique la librairie DataBase sur les tableaux +/** + * CONFIGURATION DATATABLES + * Initialise les tableaux avec exports, filtres et traductions. + */ function appliquerDataTable() { - const codeLangue = $("#codeLangue").val(); - + const codeLangue = $("#codeLangue").val() || 'fr_FR'; const translations = { fr_FR: { lengthMenu: "Affiche _MENU_ par page", - zeroRecords: "Désolé - Aucune donnée trouvée", - info: "_PAGE_ sur _PAGES_ pages", - infoEmpty: "Pas d'enregistrement", + zeroRecords: "Aucune donnée trouvée", + info: "_PAGE_ sur _PAGES_", search: "Recherche:", - paginate: { next: "►", previous: "◄", first: "|◄", last: "►|" }, - infoFiltered: "(filtré de _MAX_ total enregistrements)" + paginate: { next: "►", previous: "◄" } }, en_US: { - lengthMenu: "Display _MENU_ records per page", - zeroRecords: "Nothing found - sorry", + lengthMenu: "Display _MENU_ records", + zeroRecords: "Nothing found", info: "Showing page _PAGE_ of _PAGES_", - infoEmpty: "No records available", search: "Search:", - paginate: { next: "►", previous: "◄", first: "|◄", last: "►|" }, - infoFiltered: "(filtered from _MAX_ total records)" + paginate: { next: "►", previous: "◄" } } }; $('.tabliste').each(function() { const $table = $(this); - - // Détecter les colonnes cachées + // On récupère les colonnes marquées comme 'data-hidden' const hiddenTargets = []; - $table.find('thead th').each(function(index) { - if ($(this).data('hidden')) { - hiddenTargets.push(index); - } + $table.find('thead th').each((idx, th) => { + if ($(th).data('hidden')) hiddenTargets.push(idx); }); - const options = { + const instance = $table.DataTable({ destroy: true, responsive: true, order: [[0, "desc"]], - lengthMenu: [10, 50, 100], - pagingType: "full_numbers", - autoWidth: false, language: translations[codeLangue] || translations.fr_FR, - columnDefs: [ - { targets: hiddenTargets, visible: false, searchable: true } - ], - dom: 'Bfrtip', // B = Buttons, f = filter, r = processing, t = table, i = info, p = pagination - buttons: [ - { extend: 'copy', text: 'Copier' }, - { extend: 'csv', text: 'CSV' }, - { extend: 'excel', text: 'Excel' }, - { extend: 'pdf', text: 'PDF' }, - { extend: 'print', text: 'Imprimer' } - ] - }; + columnDefs: [{ targets: hiddenTargets, visible: false }], + dom: 'Bfrtip', + buttons: ['copy', 'csv', 'excel', 'pdf', 'print'] + }); - const instance = $table.DataTable(options); - - setTimeout(() => { - if (instance) { - instance.columns.adjust(); // recalcul des colonnes - if (instance.responsive) { - instance.responsive.recalc(); // recalcul responsive si dispo - } - } - }, 100); + // Ajustement forcé après rendu pour éviter les problèmes de colonnes mal alignées + setTimeout(() => instance.columns.adjust().responsive.recalc(), 100); }); } -function raffraichier_gabarit() -{ - - $.ajax({ - url: $("#racineWeb").val()+"Ajaxgabarit/", - success: function(data) - { - $("#div_ajaxgabarit").html(data); - - //codeSociete = $("#codeSociete").val(); - //codeLangue = $("#codeLangue").val(); - - fusionConsOrd = $("#fusionConsOrd").val(); - vue = $("#vue").val(); - - if(fusionConsOrd != "1" && vue !="Connexion"){ - - window.location.assign($("#racineWeb" ).val()+"Connexion/"); - } - - }, - error: function(errorData) - { - }, - complete: function() - { - $(".datepicker" ).datepicker(); - - raffraichier_messagerie(); - } - }); +/** + * MESSAGERIE ET NOTIFICATIONS + * Récupère le nombre de messages et déconnecte si session expirée. + */ +function raffraichier_messagerie() { + if (!navigator.onLine) { + $("#test_connexion").css('background-color', 'red'); + return; + } + + $.ajax({ + url: $("#racineWeb").val() + "Ajaxmessagerie/", + success: (data) => { + $("#nbMessagesNonLus").html(data); + $("#span_notification").text($("#msgNonLus").val()); + + // Sécurité : redirection forcée si le serveur indique une déconnexion nécessaire + if ($("#deconnexion").val() === '1') { + window.location.assign($("#racineWeb").val() + "Connexion/deconnecter/"); + } + } + }); } -function raffraichier_messagerie() -{ - deconnexion='0'; - if(navigator.onLine) - { - c_html = ""; - $.ajax({ - url: $("#racineWeb").val()+"Ajaxmessagerie/", - success: function(data) { - c_html = data; - }, - error: function(errorData) { - }, - complete: function() { - $("#nbMessagesNonLus").html(c_html); - msgNonLus=$("#msgNonLus").val(); - $("#span_notification").text(msgNonLus); - // Ajout du 27/10/2024 => déconnecter si session expirée - deconnexion=$("#deconnexion").val(); - - // alert("deconnexion => "+deconnexion); +// Réajuster les popups SweetAlert lors du redimensionnement de la fenêtre +window.addEventListener('resize', () => { + if (Swal.isVisible()) setTimeout(adjustSwalContent, 100); +}); - if(deconnexion=='1') - { - window.location.assign($("#racineWeb" ).val()+"Connexion/deconnecter/"); - } - } - }); - } - else - { - $("#test_connexion").css('background-color', 'red'); - return; - } -} \ No newline at end of file +/** + * Sélectionne une police d'assurance et met à jour les champs cachés globaux. + * Cette fonction est généralement appelée lors d'un clic sur une ligne de tableau + * ou un bouton de sélection dans une liste de polices. + */ +function selectionner_police(id, no) { + // Mise à jour de l'ID de la police dans le champ caché prévu pour le traitement serveur + $("#idPolice_C").val(id); + + // Mise à jour du numéro de police pour l'affichage ou les recherches secondaires + $("#numeroPolice_C").val(no); + + // Note : On pourrait ajouter ici un retour visuel (ex: surbrillance de la ligne sélectionnée) + console.log(`Police sélectionnée : ID ${id} / N° ${no}`); +} diff --git a/Vue/Accueil/index.php b/Vue/Accueil/index.php index 0fa97d0..0339a01 100644 --- a/Vue/Accueil/index.php +++ b/Vue/Accueil/index.php @@ -215,7 +215,7 @@ // --- Titre principal --- pdf.setFontSize(18); - pdf.text("", 10, 20); + pdf.text("", 10, 20); // --- KPIs --- pdf.setFontSize(12); diff --git a/Vue/Ajaxgabarit/index.php b/Vue/Ajaxgabarit/index.php new file mode 100644 index 0000000..074093b --- /dev/null +++ b/Vue/Ajaxgabarit/index.php @@ -0,0 +1,124 @@ + + +"> +"> + +"> +"> +"> +"> +"> +"> +"> +"> +"> + +"> +"> + +"> +"> +"> + +"> +"> + +"> +"> + +"> +"> + +"> +"> +"> + +"> +"> + +"> +"> +"> +"> + +"> +"> +"> +"> + + + + + + + +"> +"> + +"> +"> + +"> +"> + +"> +"> + +"> + +"> +"> +"> +"> + +"> + +"> + +"> +"> +"> +"> +"> +"> +"> + +"> + +"> + + + + + + + + + + + + + + + + + + + + +"> + + +"> +"> +"> + +"> +"> +"> + +"> +"> +"> diff --git a/Vue/gabarit.php b/Vue/gabarit.php index a626ac2..5e4112b 100755 --- a/Vue/gabarit.php +++ b/Vue/gabarit.php @@ -387,6 +387,7 @@ console.groupEnd();
+ ">