{% 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-12">
<div class="grid-md u-gap-2">
<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>
<div class="grid-c-6">
<fieldset>
<label for="member-number-{{ i }}"><strong>Mitgliedsnummer</strong></label>
<input type="text" id="member-number-{{ i }}" name="member_numbers[]" value="{{ current_member.memberno|default('') }}" placeholder="Mitgliedsnummer">
<p class="text-sm text-muted">Leer lassen für aktuelle Mitgliedsnummer: {{ current_member.memberno|default('Keine') }}</p>
</fieldset>
</div>
</div>
</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 %}