{% block content %} {% if modal %}<div class="modal-content">{% endif %} <div id="wa-checkin-{{ id }}" hx-target="this" hx-swap="outerHTML" class="frame"> <div class="frame__header"> <h3>Check-in</h3> <div class="grid-md u-gap-2"> <div class="grid-c-6 mb-2 mb-0-md"> <div class="u-flex u-items-center u-gap-1"> <i class="icon-uhr-outline"></i> <div class="t-label">Tag/Urzeit</div> </div> <div class="">{{ slot.time|date('d.m.Y H:i') }}</div> </div> <div class="grid-c-6 mb-2 mb-0-md"> <div class="u-flex u-items-center u-gap-1"> <i class="icon-standort-outline"></i> <div class="t-label">Standort</div> </div> <div class="">{{ standort.title }}</div> </div> <div class="grid-c-6 mb-2 mb-0-md"> <div class="u-flex u-items-center u-gap-1"> <i class="icon-behaelter-outline"></i> <div class="t-label">Gebuchte Behälterkapazität</div> </div> <div class="">{{ buchung.behaelter }}</div> </div> <div class="grid-c-6 mb-2 mb-0-md"> <div class="u-flex u-items-center u-gap-1"> <i class="icon-reben-outline"></i> <div class="t-label">Anliefernde Sorten</div> </div> <div class="">{{ buchung.sorten|join(', ') }}</div> </div> {% if slot.anmerkungen %} <div class="grid-c-12 mb-2 mb-0-md"> <div class="u-flex u-items-center u-gap-1"> <i class="icon-info-outline"></i> <div class="t-label">Anmerkungen</div> </div> {{ slot.anmerkungen|raw }} </div> {% endif %} </div> </div> <div class="divider m-0 mb-2"></div> <div class="frame__body"> <h3>Behälternummern zuweisen</h3> {% if toast is defined %} {{ toast|raw }} {% endif %} <form hx-post="/_ajax/vr_wa/v1/slot?do=updateCheckin" enctype="multipart/form-data"> <input type="hidden" name="id" value="{{ id }}"> <button type="button" id="load-numbers-btn" class="btn btn--sm btn-info mb-2">Nummern laden</button> <div class="grid-md u-gap-2"> {% for i in 1..checkin.behaelter %} <div class="grid-c-6"> <fieldset> <label for="behaelter-number-{{ i }}"><strong>Behälter {{ i }}</strong><sup class="text-danger">*</sup></label> <select id="behaelter-number-{{ i }}" name="behaelter_numbers[]" required data-loaded="false"> <option value="">-</option> <!-- Options will be loaded via Ajax when the select is focused --> </select> </fieldset> </div> {% endfor %} </div> <fieldset> <button id="checkin-submit" type="submit">Check-in durchführen</button> </fieldset> </form> </div> </div> {% if modal %}</div>{% endif %} {% endblock %} {% block modal %} {% if modal %} <style> .loading-spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid rgba(0, 0, 0, 0.1); border-left-color: #3498db; border-radius: 50%; animation: spin 1s linear infinite; vertical-align: middle; margin-right: 5px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #numbers-loading { display: flex; align-items: center; margin: 10px 0; } /* Style for the special option "Nummer nicht bekannt" */ .special-option { font-weight: bold; color: #3498db; border-bottom: 1px dashed #ccc; margin-bottom: 5px; } </style> <script> (function ($) { window.modals = window.modals || [] window.modals.wa_checkins = window.modals.wa_checkins || [] if (window.modals.wa_checkins.details{{ id }} === undefined) { window.modals.wa_checkins.details{{ id }} = new jBox('Modal', { closeButton: 'box', content: $('#wa-checkin-{{ id }}'), maxWidth: 650, minWidth: 100, minHeight: 100, width: 650, overlay: true, closeOnClick: false, zIndex: 'auto', addClass: '', onOpen: function() { // Initialize the dynamic select behavior initDynamicSelects(); } }).open(); } else { window.modals.wa_checkins.details{{ id }}.content.empty(); window.modals.wa_checkins.details{{ id }}.setContent($('#wa-checkin-{{ id }}')).open(); // Initialize the dynamic select behavior setTimeout(initDynamicSelects, 100); // Small delay to ensure DOM is ready } // Function to initialize the dynamic select behavior function initDynamicSelects() { // Cache DOM elements and data var $selects = $('#wa-checkin-{{ id }} select[name="behaelter_numbers[]"]'); var allOptionsArray = []; // Store all available options as a flat array var selectsCount = $selects.length; var previousValues = {}; // Track previous values to detect changes var optionElements = {}; // Pre-created option elements for better performance var isLoadingNumbers = false; // Flag to prevent multiple simultaneous requests var numbersLoaded = false; // Flag to track if numbers have been loaded // Function to load available numbers via Ajax function loadAvailableNumbers() { if (isLoadingNumbers || numbersLoaded) { return; } isLoadingNumbers = true; // Show loading animation $selects.prop('disabled', true); $selects.first().after('<div id="numbers-loading" class="text-sm text-muted"><div class="loading-spinner"></div> Lade verfügbare Nummern...</div>'); // Make Ajax request to get available numbers $.ajax({ url: '/_ajax/vr_wa/v1/slot', method: 'GET', data: { do: 'getAvailableNumbers', id: '{{ id }}', limit: 500 // Adjust this based on your needs }, dataType: 'json', success: function(response) { if (response.numbers && response.numbers.length > 0) { // Store the numbers in the allOptionsArray allOptionsArray = response.numbers.map(function(number) { // Special handling for the value 9999 if (number === '9999') { return { value: number, text: 'Nummer nicht bekannt', isSpecial: true }; } return { value: number, text: number }; }); // Pre-create option elements for better performance allOptionsArray.forEach(function(option) { optionElements[option.value] = $('<option>', { value: option.value, text: option.text, class: option.isSpecial ? 'special-option' : '' })[0]; // Get the raw DOM element for better performance }); // Update all selects with the available numbers updateSelectOptions(); // Mark all selects as loaded $selects.attr('data-loaded', 'true'); numbersLoaded = true; } else { // Handle case where no numbers are available $selects.first().after('<div class="text-sm text-danger">Keine Nummern verfügbar</div>'); } }, error: function(xhr) { var errorMessage = 'Fehler beim Laden der Nummern'; try { var response = JSON.parse(xhr.responseText); if (response.error) { errorMessage = response.error; } } catch (e) {} $selects.first().after('<div class="text-sm text-danger">' + errorMessage + '</div>'); }, complete: function() { // Remove loading indicator and re-enable selects $('#numbers-loading').remove(); $selects.prop('disabled', false); isLoadingNumbers = false; } }); } // Function to efficiently update select options function updateSelectOptions(changedSelect) { // If numbers haven't been loaded yet, don't update if (allOptionsArray.length === 0) { return; } // Show a brief loading animation when refreshing values if (changedSelect) { var $loadingIndicator = $('<div class="loading-spinner" style="position: absolute; right: 25px; top: 50%; transform: translateY(-50%);"></div>'); $(changedSelect).parent().css('position', 'relative').append($loadingIndicator); // Remove the loading indicator after a short delay setTimeout(function() { $loadingIndicator.remove(); }, 500); } // Get all currently selected values var selectedValues = {}; var hasChanges = false; $selects.each(function() { var val = $(this).val(); if (val) { selectedValues[val] = true; } }); // Only update other selects if there's a change $selects.each(function() { var $select = $(this); var selectId = $select.attr('id'); var currentValue = $select.val(); // Skip the select that triggered the change and selects that don't need updating if (changedSelect && this === changedSelect && currentValue) { previousValues[selectId] = currentValue; return; } // Check if we need to update this select var needsUpdate = false; if (changedSelect) { needsUpdate = true; // Always update on explicit change } else if (!previousValues[selectId] || previousValues[selectId] !== currentValue) { needsUpdate = true; } if (needsUpdate) { // Store current selection var currentSelection = currentValue; // Efficiently update options var optionsFragment = document.createDocumentFragment(); var emptyOption = $select.find('option:first').clone()[0]; optionsFragment.appendChild(emptyOption); // First add the special option (9999) if it's available var specialOption = null; var regularOptions = []; // Separate special option from regular options allOptionsArray.forEach(function(option) { // Always include the special option (9999) or options that are either the current selection or not selected elsewhere if (option.isSpecial || option.value === currentSelection || !selectedValues[option.value]) { if (option.isSpecial) { specialOption = option; } else { regularOptions.push(option); } } }); // Add special option first (after empty option) if (specialOption) { var specialOptionClone = optionElements[specialOption.value].cloneNode(true); optionsFragment.appendChild(specialOptionClone); } // Then add all other options regularOptions.forEach(function(option) { var optionClone = optionElements[option.value].cloneNode(true); optionsFragment.appendChild(optionClone); }); // Replace all options at once for better performance $select.empty()[0].appendChild(optionsFragment); // Restore current selection if (currentSelection) { $select.val(currentSelection); } previousValues[selectId] = currentSelection; } }); } // Add change event to all selects $selects.on('change', function() { updateSelectOptions(this); }); // Add focus event to all selects to load numbers when first focused $selects.on('focus', function() { if (!numbersLoaded) { loadAvailableNumbers(); } }); // Add click handler to the load numbers button $('#load-numbers-btn').on('click', function(e) { e.preventDefault(); // Add loading animation to button var $btn = $(this); var originalText = $btn.text(); $btn.html('<div class="loading-spinner" style="display: inline-block;"></div> Wird geladen...').prop('disabled', true); // Store original button state var restoreButton = function() { $btn.html(originalText).prop('disabled', false); }; // Override the complete callback of the Ajax request var originalComplete = $.ajax.prototype.complete; $.ajax.prototype.complete = function() { restoreButton(); $.ajax.prototype.complete = originalComplete; return originalComplete.apply(this, arguments); }; loadAvailableNumbers(); // Fallback in case the Ajax request fails or takes too long setTimeout(restoreButton, 10000); }); } })(jQuery); </script> {% endif %} {% endblock %}