1function updateLegalRepresentativeVisibility(eventAudience) { 2 var $section = $('#legal-representative-section'); 3 if ($section.length === 0) { 4 return; 5 } 6 7 var hasAdult = false; 8 var hasChild = false; 9 10 $('select[name*="participants"], input[name*="participants"]').each(function() { 11 var name = $(this).attr('name') || ''; 12 if (name.indexOf('type') !== -1) { 13 var value = $(this).val(); 14 if (value === 'ADULT') { 15 hasAdult = true; 16 } else if (value === 'CHILD' ) { 17 hasChild = true; 18 } 19 } 20 }); 21 22 // Afficher la section représentant légal uniquement si: 23 // - L'audience est CHILD_ONLY 24 // - OU il y a au moins un enfant ET aucun adulte parmi les participants 25 var shouldShow = eventAudience === 'CHILD_ONLY' || (hasChild && !hasAdult); 26 27 if (shouldShow) { 28 $section.find('input').prop('required', false).removeAttr('required'); 29 $section.show(); 30 $('#legal_representative_firstname, #legal_representative_lastname, #legal_representative_email, #legal_representative_phone_number').prop('required', true); 31 } else { 32 $section.find('input').prop('required', false).removeAttr('required') 33 .each(function() { this.setCustomValidity(''); }); 34 $section.hide(); 35 } 36} 37 38function updateParticipantFieldsVisibility($row, eventAudience) { 39 var $typeField = $row.find('select[name*="[type]"], input[name*="[type]"]'); 40 var typeValue = $typeField.val(); 41 42 var isChild = (typeValue === 'CHILD' || eventAudience === 'CHILD_ONLY'); 43 var isAdult = (typeValue === 'ADULT' || eventAudience === 'ADULT_ONLY'); 44 45 var $emailField = $row.find('.participant-email-field'); 46 var $birthdateField = $row.find('.participant-birthdate-field'); 47 48 if (isChild) { 49 // Enfant : cacher email, afficher date de naissance 50 $emailField.hide(); 51 $emailField.find('input').prop('required', false).removeAttr('required') 52 .each(function() { this.setCustomValidity(''); }); 53 $birthdateField.show(); 54 $birthdateField.find('input').prop('required', true); 55 } else if (isAdult) { 56 // Adulte : afficher email, cacher date de naissance 57 $emailField.show(); 58 $emailField.find('input').prop('required', true); 59 $birthdateField.hide(); 60 $birthdateField.find('input').prop('required', false).removeAttr('required') 61 .each(function() { this.setCustomValidity(''); }); 62 } else { 63 // Pas encore sélectionné (audience ALL) : afficher email, cacher date de naissance 64 $emailField.show(); 65 $emailField.find('input').prop('required', true); 66 $birthdateField.hide(); 67 $birthdateField.find('input').prop('required', false).removeAttr('required') 68 .each(function() { this.setCustomValidity(''); }); 69 } 70} 71 72function updateAllParticipantsFieldsVisibility(eventAudience) { 73 $('.participants-row').each(function() { 74 updateParticipantFieldsVisibility($(this), eventAudience); 75 }); 76} 77 78function updateRegisterButton(modal) { 79 const registerBtn = modal.querySelector(".btn-register-occurrences"); 80 if (!registerBtn) return; 81 const anyChecked = modal.querySelectorAll(".occurrence-checkbox:checked").length > 0; 82 registerBtn.disabled = !anyChecked; 83 registerBtn.style.cursor = anyChecked ? "pointer" : "not-allowed"; 84 registerBtn.style.pointerEvents = anyChecked ? "auto" : "none"; 85} 86 87function setupBonusFiltering(modalDetails, allowWaitingList) { 88 const yesBtn = modalDetails.querySelector('#bonus-yes'); 89 const noBtn = modalDetails.querySelector('#bonus-no'); 90 const rows = modalDetails.querySelectorAll('.modal-event-row'); 91 const checkboxes = modalDetails.querySelectorAll('.occurrence-checkbox'); 92 93 const eventSubtitle = modalDetails.querySelector('.event-subtitle'); 94 const checkAllContainer = modalDetails.querySelector('.check-all-container'); 95 96 rows.forEach(row => row.style.cssText = "display:none !important;"); 97 98 if (eventSubtitle) eventSubtitle.style.display = "none"; 99 if (checkAllContainer) checkAllContainer.style.display = "none"; 100 101 updateRegisterButton(modalDetails); 102 103 const handleChoice = (showBonusOnly) => { 104 modalDetails.dataset.bonusChoice = showBonusOnly ? "yes" : "no"; 105 106 if (eventSubtitle) eventSubtitle.style.display = ""; 107 if (checkAllContainer) checkAllContainer.style.display = ""; 108 109 yesBtn.classList.toggle('bonus-selected', showBonusOnly); 110 noBtn.classList.toggle('bonus-selected', !showBonusOnly); 111 112 let hiddenRows = 0; 113 114 rows.forEach(row => { 115 const hasBonus = row.dataset.hasBonus === '1'; 116 const checkbox = row.querySelector('.occurrence-checkbox'); 117 const availableNormal = parseInt(row.dataset.availableNormal || "0");
118 const availableBonus = parseInt(row.dataset.availableBonus || "0"); 119 120 const shouldShow = showBonusOnly ? hasBonus : true; 121 row.style.display = shouldShow ? "" : "none"; 122 if (!shouldShow) { 123 hiddenRows++; 124 } 125 126 const normalDiv = row.querySelector('.normal-places'); 127 const bonusDiv = row.querySelector('.bonus-places'); 128 129 if (normalDiv) normalDiv.style.display = showBonusOnly ? "none" : "flex"; 130 if (bonusDiv) bonusDiv.style.display = showBonusOnly ? "flex" : "none"; 131 132 let shouldDisable; 133 134 if (showBonusOnly) { 135 shouldDisable = availableBonus <= 0; 136 } else { 137 shouldDisable = !allowWaitingList && availableNormal <= 0; 138 } 139 140 if (shouldDisable) { 141 row.classList.add('is-disabled'); 142 if (checkbox) checkbox.disabled = true; 143 } else { 144 row.classList.remove('is-disabled'); 145 if (checkbox) checkbox.disabled = false; 146 } 147 148 }); 149 150 if (checkAllContainer) { 151 if (showBonusOnly && hiddenRows > 0) modalDetails.querySelector('.check-all-container label').style.display = 'none'; 152 else modalDetails.querySelector('.check-all-container label').style.display = ''; 153 } 154 155 if (rows.length > 1) { 156 checkboxes.forEach(cb => cb.checked = false); 157 } 158 const checkAll = checkAllContainer?.querySelector('.check-all-occurrences'); 159 if (checkAll) { 160 checkAll.checked = false; 161 checkAll.indeterminate = false; 162 } 163 164 if (rows.length === 1) { 165 const singleRow = rows[0]; 166 const singleCheckbox = singleRow.querySelector('.occurrence-checkbox'); 167 if (singleCheckbox) { 168 singleCheckbox.checked = singleRow.style.display !== 'none' && !singleCheckbox.disabled; 169 } 170 } 171 updateRegisterButton(modalDetails); 172 } 173 yesBtn.addEventListener('click', () => handleChoice(true)); 174 noBtn.addEventListener('click', () => handleChoice(false)); 175} 176 177function getModals(eventId, occurrenceId, url, options = {}) { 178 const { autoCheckOccurrence = true, checkForBonusPlaces = false } = options; 179 180 fetch(window.location.origin + url, { 181 method: "POST", 182 headers: { "Content-Type": "application/json" }, 183 body: JSON.stringify({ eventId, occurrenceId, checkForBonusPlaces }), 184 }) 185 .then((response) => response.json()) 186 .then(({event, modal}) => { 187 // Supprime les anciennes modals 188 const oldEventDetails = document.getElementById("event-details-" + occurrenceId); 189 const oldRegistration = document.getElementById("registration-modal-" + occurrenceId); 190 if (oldEventDetails) oldEventDetails.remove(); 191 if (oldRegistration) oldRegistration.remove(); 192 193 // Injecte les modals 194 document.body.insertAdjacentHTML("beforeend", modal); 195 const modalDetails = document.getElementById("event-details-" + occurrenceId); 196 if (modalDetails) { 197 modalDetails.style.overflowX = "hidden"; 198 modalDetails.style.overflowY = "hidden"; 199 updateOccurrencesScroll(occurrenceId); 200 } 201 202 setupEventDetailsModal(event, occurrenceId, autoCheckOccurrence); 203 204 if (event.hasBonusPlaces) { 205 setupBonusFiltering(modalDetails, event.allowWaitingList); 206 } 207 208 // Affiche la modal détails 209 $("#event-details-" + occurrenceId).modal("show"); 210 211 const eventAudience = event.targetAudience || ''; 212 const modal_form = document.getElementById("registration-modal-" + occurrenceId); 213 const add_participant_button = modal_form.querySelector("#add-participant"); 214 add_participant_button.addEventListener('click', () => addParticipant(event, occurrenceId, modal_form, eventAudience)); 215 216 const typeFields = modal_form.querySelectorAll('#type, select[name*="type"], input[name*="type"]'); 217 typeFields.forEach(field => { 218 field.addEventListener('change', function() { 219 updateLegalRepresentativeVisibility(eventAudience); 220 const $row = $(this).closest('.participants-row'); 221 updateParticipantFieldsVisibility($row, eventAudience); 222 }); 223 }); 224 225 updateLegalRepresentativeVisibility(eventAudience); 226 updateAllParticipantsFieldsVisibility(eventAudience); 227 228 }) 229 .catch((err) => console.error(err)); 230} 231 232function setupEventDetailsModal(event, occurrenceId, preCheckedId = false) { 233 const modal = document.getElementById("event-details-" + occurrenceId); 234 if (!modal) return; 235 236 const registerBtn = modal.querySelector(".btn-register-occurrences"); 237 const checkboxes = modal.querySelectorAll(".occurrence-checkbox"); 238 const checkAll = modal.querySelector(".check-all-occurrences"); 239 240 if (preCheckedId) { 241 const checkboxToCheck = modal.querySelector(`.occurrence-checkbox[value="${occurrenceId}"]`);
242 if (checkboxToCheck && !checkboxToCheck.disabled) { 243 checkboxToCheck.checked = true; 244 } 245 } 246 247 updateRegisterButton(modal); 248 // Checkbox "check all" 249 if (checkAll) { 250 checkAll.addEventListener("change", function () { 251 checkboxes.forEach((cb) => { 252 if (!cb.disabled) cb.checked = checkAll.checked; 253 }); 254 updateRegisterButton(modal); 255 }); 256 } 257 258 // Checkbox individuelles 259 checkboxes.forEach((cb) => 260 cb.addEventListener("change", () => updateRegisterButton(modal)), 261 ); 262 263 // Bouton "S'inscrire" 264 registerBtn.addEventListener("click", function () { 265 const selected = modal.querySelectorAll(".occurrence-checkbox:checked"); 266 const ids = Array.from(selected).map((c) => c.value); 267 268 const occurrences = getOccurrencesFromEvent(event, ids); 269 const bonusChoice = modal.dataset.bonusChoice; 270 const isBonus = bonusChoice === "yes" ? 1 : 0; 271 272 // Affiche le modal d'inscription 273 $("#event-details-" + occurrenceId).modal("hide"); 274 $("#registration-modal-" + occurrenceId).modal("show"); 275 276 // Une fois le modal d'inscription affiché, remplir l'input hidden et ajouter 1 participant 277 $("#registration-modal-" + occurrenceId) 278 .off("shown.bs.modal") 279 .on("shown.bs.modal", function () { 280 281 // clear the modal avoiding glitch to submit multiple participant when it's not allowed 282 document.querySelectorAll(".participants-row:not(:first-child)").forEach(p => p.remove()); 283 284 setupRegistrationSubmit(occurrenceId, ids, isBonus); 285 updateModalScroll(occurrenceId); 286 287 const modal_form = document.getElementById("registration-modal-" + occurrenceId); 288 handleParticipantSuppression(modal_form, event, occurrenceId, occurrences); 289 290 // Ajouter l'action de suppression sur le bouton du premier participant 291 const participant = document.querySelector(".participants-row"); 292 const suppress_button = participant.querySelector(".remove-participant"); 293 suppress_button.addEventListener('click', () => { 294 participant.remove(); 295 handleParticipantSuppression(modal_form, event, occurrenceId, occurrences); 296 }); 297 }); 298 299 // Quand le modal d'inscription se ferme, réaffiche le modal détails 300 $("#registration-modal-" + occurrenceId) 301 .off("hidden.bs.modal") 302 .on("hidden.bs.modal", function () { 303 $("#event-details-" + occurrenceId).modal("show"); 304 }); 305 }); 306} 307 308// Scroll dynamique de la modal inscription 309function updateModalScroll(occurrenceId) { 310 const modalBody = document.querySelector( 311 "#registration-modal-" + occurrenceId + " .modal-body", 312 ); 313 314 if (!modalBody) return; 315 const modalContent = modalBody.closest(".modal-content"); 316 const headerHeight = 317 modalContent.querySelector(".modal-header").offsetHeight; 318 const footerHeight = 319 modalContent.querySelector(".modal-footer").offsetHeight; 320 modalBody.style.maxHeight = `calc(90vh - ${headerHeight + footerHeight + 40}px)`; 321 modalBody.style.overflowY = "auto"; 322} 323 324function updateOccurrencesScroll(occurrenceId) { 325 const occurrencesList = document.querySelector(`#event-details-${occurrenceId} .occurences-list`); 326 const modalContent = occurrencesList?.closest(".modal-content"); 327 if (!occurrencesList || !modalContent) return; 328 329 const headerHeight = modalContent.querySelector(".modal-header")?.offsetHeight || 0; 330 const footerHeight = modalContent.querySelector(".modal-footer")?.offsetHeight || 0; 331 const extraSpacing = 20; 332 333 occurrencesList.style.maxHeight = `calc(45vh - ${headerHeight + footerHeight + extraSpacing}px)`; 334 occurrencesList.style.overflowY = "auto"; 335 occurrencesList.style.overflowX = "hidden"; 336} 337 338function handleParticipantSuppression(modal, event, occurrenceId, occurrences) { 339 updateAddParticipantButtonState(event, occurrenceId, occurrences); 340 const participants = modal.querySelectorAll(".participants-row"); 341 if (participants.length === 1) { 342 const suppress_button = participants[0].querySelector( 343 ".remove-participant", 344 ); 345 suppress_button.style.display = "none"; 346 } else if (participants.length > 1) {
347 participants.forEach((participant) => { 348 const suppress_button = participant.querySelector( 349 ".remove-participant", 350 ); 351 suppress_button.style.display = "block"; 352 }) 353 } 354} 355 356function addParticipant(event, occurrenceId, modal, eventAudience) { 357 const container = modal.querySelector("#participants-container"); 358 const rows = container.querySelectorAll(".participants-row"); 359 360 if (rows.length === 0) return; 361 362 const lastRow = rows[rows.length - 1]; 363 const copy = lastRow.cloneNode(true); 364 365 copy.querySelectorAll("input, select, textarea").forEach(field => { 366 367 if (field.type === "checkbox" || field.type === "radio") { 368 field.checked = false; 369 } else { 370 if (!field.name.endsWith("[type]")) { 371 field.value = ""; 372 } 373 } 374 375 if (field.id) { 376 field.removeAttribute("id"); 377 } 378 }); 379 380 container.appendChild(copy); 381 reindexParticipants(modal); 382 const suppressButton = copy.querySelector(".remove-participant"); 383 suppressButton.style.display = "block"; 384 // Extraire l'eventId depuis l'id du modal 385 var eventId = event.id; 386 387 const modal_occurrences = document.getElementById('event-details-' + occurrenceId); 388 const selected = modal_occurrences.querySelectorAll(".occurrence-checkbox:checked"); 389 const ids = Array.from(selected).map((c) => c.value); 390 const occurrences = getOccurrencesFromEvent(event, ids); 391 suppressButton.addEventListener("click", () => { 392 copy.remove(); 393 reindexParticipants(modal); 394 395 handleParticipantSuppression(modal, event, occurrenceId, occurrences); 396 397 updateModalScroll(eventId); 398 // Mettre à jour la visibilité de la section représentant légal 399 updateLegalRepresentativeVisibility(eventAudience); 400 }); 401 402 // Ajouter l'écouteur de changement de type sur le nouveau participant 403 copy.querySelectorAll('select[name*="[type]"], input[name*="[type]"]').forEach(field => { 404 field.addEventListener('change', function() { 405 updateLegalRepresentativeVisibility(eventAudience); 406 // Mettre à jour les champs du participant concerné 407 var $row = $(this).closest('.participants-row'); 408 updateParticipantFieldsVisibility($row, eventAudience); 409 }); 410 }); 411 412 handleParticipantSuppression(modal, event, occurrenceId, occurrences); 413 updateModalScroll(eventId); 414 // Mettre à jour la visibilité de la section représentant légal 415 updateLegalRepresentativeVisibility(eventAudience); 416 // Initialiser la visibilité des champs pour le nouveau participant 417 updateParticipantFieldsVisibility($(copy), eventAudience); 418} 419 420function reindexParticipants(modal) { 421 const rows = modal.querySelectorAll(".participants-row"); 422 rows.forEach((row, index) => { 423 row.querySelectorAll("[name]").forEach(field => { 424 field.name = field.name.replace( 425 /participants\[\d+]/, 426 `participants[${index}]` 427 ); 428 }); 429 }); 430} 431 432function setupRegistrationSubmit(occurrenceId, ids, isBonus = 0) { 433 const form = document.getElementById(`registration-form-${occurrenceId}`); 434 const modal = document.getElementById(`registration-modal-${occurrenceId}`); 435 436 // A cause de la manière dont sont géré les évènements et le fait qu'on soit en vanilla, on doit faire ça 437 // pour éviter de créer plusieurs inscriptions à la fois 438 const oldBtn = modal.querySelector("#submit-registration"); 439 const newBtn = oldBtn.cloneNode(true); 440 oldBtn.parentNode.replaceChild(newBtn, oldBtn); 441 442 const bonusField = modal.querySelector('#is-bonus-registration'); 443 if (bonusField) bonusField.value = isBonus; 444 445 newBtn.addEventListener("click", function handler() { 446 if (!form.checkValidity()) { 447 form.reportValidity(); 448 return; 449 } 450 451 newBtn.removeEventListener("click", handler); 452 453 const formData = new FormData(form); 454 ids.forEach(id => formData.append('occurrenceIds[]', id)); 455 456 formData.set('isBonusRegistration', isBonus); 457 458 fetch('/registration/create', { 459 method: 'POST', 460 body: formData, 461 headers: { 'X-Requested-With': 'XMLHttpRequest' } 462 }) 463 .then(response => response.json()) 464 .then(json => { 465 window.location.reload(); 466 }) 467 .catch(json => { 468 window.location.reload(); 469 }); 470 }); 471} 472 473function displayResponse(registrationResult) { 474 let icon = 'fa fa-check-square'; 475 let msg = registrationResult.message; 476 let type = 'success'; 477 478 if (! registrationResult.success) { 479 icon = 'fa fa-exclamation-triangle'; 480 type = 'danger'; 481 } 482 483 $.notify( 484 { 485 icon: icon, 486 message: msg, 487 }, 488 { 489 type: type, 490 newest_on_top: true, 491 delay: 3000, 492 } 493 ); 494} 495 496function updateAddParticipantButtonState(event, occurrenceId, occurrences) { 497 const modal = document.getElementById('registration-modal-' + occurrenceId); 498 const addParticipantButton = modal.querySelector('#add-participant'); 499 const participantsNumber = modal.querySelectorAll('.participants-row').length; 500 const eventDetailModal = document.getElementById("event-details-" + occurrenceId); 501 const bonusChoice = eventDetailModal.dataset.bonusChoice; 502 const isBonus = bonusChoice === "yes" ? 1 : 0; 503 let occurrencesWithLessAvailablePlaces = occurrences.reduce((prev, curr) => prev.availableAllPlaces < curr.availableAllPlaces ? prev : curr).availableAllPlaces; 504 505 if (isBonus) {
506 const lessAvailableBonusPlaces = occurrences.reduce((prev, curr) => prev.availableBonusPlaces < curr.availableBonusPlaces ? prev : curr); 507 occurrencesWithLessAvailablePlaces = lessAvailableBonusPlaces.availableBonusPlaces; 508 } 509 510 const maxSimultaneousParticipantsAllowed = getMaxSimultaneousParticipantsForSelectedOccurrences(occurrences); 511 512 let maxAllowed; 513 514 if (event.allowWaitingList && !isBonus) { 515 maxAllowed = maxSimultaneousParticipantsAllowed; 516 } else { 517 maxAllowed = Math.min(occurrencesWithLessAvailablePlaces, maxSimultaneousParticipantsAllowed); 518 } 519 520 const addParticipantAllowed = participantsNumber < maxAllowed; 521 522 const errorDiv = modal.querySelector("#participant-error"); 523 const maxAllowedParticipantContent = errorDiv.textContent; 524 525 if (addParticipantAllowed) { 526 addParticipantButton.style.display = "block"; 527 errorDiv.style.display = "none"; 528 } else { 529 addParticipantButton.style.display = "none"; 530 errorDiv.style.display = "block"; 531 errorDiv.textContent = maxAllowedParticipantContent.replace('{X}', maxAllowed); 532 } 533} 534 535function getOccurrencesFromEvent(event, ids = []) { 536 if (!Array.isArray(ids) || ids.length === 0) { 537 return event.occurrences; 538 } else { 539 let idsAsInt = []; 540 ids.forEach((id) => { 541 idsAsInt.push(parseInt(id)); 542 }); 543 let result = []; 544 event.occurrences.forEach((occurrence) => { 545 if (idsAsInt.includes(occurrence.id)) { 546 result.push(occurrence); 547 } 548 }); 549 return result; 550 } 551} 552 553function getMaxSimultaneousParticipantsForSelectedOccurrences(occurrences) { 554 let lower = occurrences[0].nbSimultaneousParticipants; 555 occurrences.forEach((occurrence) => { 556 if (occurrence.nbSimultaneousParticipants < lower) { 557 lower = occurrence.nbSimultaneousParticipants; 558 } 559 }); 560 return lower; 561} 562 563function updateRegistrationButtonsStatus() { 564 let eventIds = []; 565 document.querySelectorAll(".occurrences-block").forEach((div) => { 566 let eventId = div.dataset.apiid; 567 if (!window.updatedEvents.includes(eventId)) { 568 eventIds.push(eventId); 569 window.updatedEvents.push(eventId); 570 } 571 }); 572 573 if (eventIds.length === 0) return; 574 575 fetch('/registration/buttons', { 576 method: "POST", 577 headers: { "Content-Type": "application/json" }, 578 body: JSON.stringify({ 579 'referer': window.location.href, 580 'eventIds': eventIds 581 }) 582 }) 583 .then(response => response.json()) 584 .then(json => { 585 Object.entries(json.data).forEach(([id, html]) => { 586 document.querySelectorAll(`#occ-${id}`).forEach(c => { 587 c.querySelector(".event-register").innerHTML = html; 588 }); 589 }) 590 }).catch(err => console.error(err)); 591} 592 593document.addEventListener('DOMContentLoaded', function () { 594 window.updatedEvents = []; 595 updateRegistrationButtonsStatus(); 596 window.addEventListener('scroll', updateRegistrationButtonsStatus); 597 598 document.body.addEventListener('click', function (e) { 599 600 const btn = e.target.closest('[id^="open-registration-"]'); 601 if (!btn) return; 602 603 e.preventDefault(); 604 e.stopImmediatePropagation(); 605 606 const eventId = btn.dataset.eventId; 607 const url = btn.dataset.url; 608 const occurrenceId = btn.dataset.occurrenceId; 609 610 if (!eventId || !url || !occurrenceId) { 611 console.error("Données manquantes sur le bouton", btn); 612 return; 613 } 614 615 const existingModal = document.getElementById("event-details-" + occurrenceId); 616 617 if (existingModal) { 618 $("#event-details-" + occurrenceId).modal("show"); 619 return; 620 } 621 622 getModals(eventId, occurrenceId, url); 623 }); 624});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.