| ... | ... |
@@ -60,6 +60,11 @@ |
| 60 | 60 |
|
| 61 | 61 |
<button type="button" id="load-numbers-btn" class="btn btn--sm btn-info mb-2">Nummern laden</button> |
| 62 | 62 |
|
| 63 |
+ <!-- Datalist for all behaelter number inputs --> |
|
| 64 |
+ <datalist id="behaelter-numbers-list"> |
|
| 65 |
+ <!-- Options will be loaded via Ajax --> |
|
| 66 |
+ </datalist> |
|
| 67 |
+ |
|
| 63 | 68 |
<div class="grid-md u-gap-2"> |
| 64 | 69 |
{% for i in 1..checkin.behaelter %}
|
| 65 | 70 |
<div class="grid-c-12"> |
| ... | ... |
@@ -67,16 +72,27 @@ |
| 67 | 72 |
<div class="grid-c-6"> |
| 68 | 73 |
<fieldset> |
| 69 | 74 |
<label for="behaelter-number-{{ i }}"><strong>Behälter {{ i }}</strong><sup class="text-danger">*</sup></label>
|
| 70 |
- <select id="behaelter-number-{{ i }}" name="behaelter_numbers[]" required data-loaded="false">
|
|
| 71 |
- <option value="">-</option> |
|
| 72 |
- <!-- Options will be loaded via Ajax when the select is focused --> |
|
| 73 |
- </select> |
|
| 75 |
+ <input type="text" id="behaelter-number-{{ i }}" name="behaelter_numbers[]" required data-loaded="false" list="behaelter-numbers-list" autocomplete="off"
|
|
| 76 |
+ {% if form_data is defined and form_data.behaelter_numbers[i-1] is defined %}
|
|
| 77 |
+ value="{{ form_data.behaelter_numbers[i-1] }}"
|
|
| 78 |
+ {% if form_data.invalid_fields is defined and (i-1) in form_data.invalid_fields %}
|
|
| 79 |
+ class="is-invalid" |
|
| 80 |
+ {% endif %}
|
|
| 81 |
+ {% endif %}
|
|
| 82 |
+ > |
|
| 83 |
+ {% if form_data is defined and form_data.invalid_fields is defined and (i-1) in form_data.invalid_fields %}
|
|
| 84 |
+ <div id="behaelter-number-{{ i }}-validation" class="validation-message error">
|
|
| 85 |
+ Bitte korrigieren Sie dieses Feld |
|
| 86 |
+ </div> |
|
| 87 |
+ {% endif %}
|
|
| 74 | 88 |
</fieldset> |
| 75 | 89 |
</div> |
| 76 | 90 |
<div class="grid-c-6"> |
| 77 | 91 |
<fieldset> |
| 78 | 92 |
<label for="member-number-{{ i }}"><strong>Mitgliedsnummer</strong></label>
|
| 79 |
- <input type="text" id="member-number-{{ i }}" name="member_numbers[]" value="{{ current_member.memberno|default('') }}" placeholder="Mitgliedsnummer">
|
|
| 93 |
+ <input type="text" id="member-number-{{ i }}" name="member_numbers[]"
|
|
| 94 |
+ value="{% if form_data is defined and form_data.member_numbers[i-1] is defined %}{{ form_data.member_numbers[i-1] }}{% else %}{{ current_member.memberno|default('') }}{% endif %}"
|
|
| 95 |
+ placeholder="Mitgliedsnummer"> |
|
| 80 | 96 |
<p class="text-sm text-muted">Leer lassen für aktuelle Mitgliedsnummer: {{ current_member.memberno|default('Keine') }}</p>
|
| 81 | 97 |
</fieldset> |
| 82 | 98 |
</div> |
| ... | ... |
@@ -127,9 +143,89 @@ |
| 127 | 143 |
border-bottom: 1px dashed #ccc; |
| 128 | 144 |
margin-bottom: 5px; |
| 129 | 145 |
} |
| 146 |
+ |
|
| 147 |
+ /* Style for datalist options */ |
|
| 148 |
+ option {
|
|
| 149 |
+ padding: 5px; |
|
| 150 |
+ font-size: 14px; |
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ /* Style for input fields with datalist */ |
|
| 154 |
+ input[list] {
|
|
| 155 |
+ background-color: #fff; |
|
| 156 |
+ border: 1px solid #ccc; |
|
| 157 |
+ border-radius: 4px; |
|
| 158 |
+ padding: 8px 12px; |
|
| 159 |
+ width: 100%; |
|
| 160 |
+ box-sizing: border-box; |
|
| 161 |
+ } |
|
| 162 |
+ |
|
| 163 |
+ input[list]:focus {
|
|
| 164 |
+ border-color: #3498db; |
|
| 165 |
+ outline: none; |
|
| 166 |
+ box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); |
|
| 167 |
+ } |
|
| 168 |
+ |
|
| 169 |
+ /* Validation styles */ |
|
| 170 |
+ input[list].is-invalid {
|
|
| 171 |
+ border-color: #dc3545; |
|
| 172 |
+ box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.2); |
|
| 173 |
+ } |
|
| 174 |
+ |
|
| 175 |
+ input[list].is-valid {
|
|
| 176 |
+ border-color: #28a745; |
|
| 177 |
+ box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.2); |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ .validation-message {
|
|
| 181 |
+ font-size: 12px; |
|
| 182 |
+ margin-top: 4px; |
|
| 183 |
+ display: none; |
|
| 184 |
+ } |
|
| 185 |
+ |
|
| 186 |
+ .validation-message.error {
|
|
| 187 |
+ color: #dc3545; |
|
| 188 |
+ display: block; |
|
| 189 |
+ } |
|
| 130 | 190 |
</style> |
| 131 | 191 |
<script> |
| 132 | 192 |
(function ($) {
|
| 193 |
+ // Store the initDynamicSelects function in a global scope for reuse |
|
| 194 |
+ window.initDynamicSelectsForCheckin = null; |
|
| 195 |
+ window.htmxSwapOccurred = false; |
|
| 196 |
+ |
|
| 197 |
+ // Add event listeners for HTMX events |
|
| 198 |
+ document.body.addEventListener('htmx:afterSwap', function(event) {
|
|
| 199 |
+ // Check if the swapped element is our check-in form |
|
| 200 |
+ if (event.detail.target && event.detail.target.id && event.detail.target.id.startsWith('wa-checkin-')) {
|
|
| 201 |
+ console.log('HTMX content swapped, reinitializing JavaScript');
|
|
| 202 |
+ // Set flag to indicate a swap occurred |
|
| 203 |
+ window.htmxSwapOccurred = true; |
|
| 204 |
+ // Use the stored function if available |
|
| 205 |
+ if (window.initDynamicSelectsForCheckin) {
|
|
| 206 |
+ window.initDynamicSelectsForCheckin(); |
|
| 207 |
+ } else {
|
|
| 208 |
+ console.error('initDynamicSelectsForCheckin function not available');
|
|
| 209 |
+ // Try to reinitialize the modal |
|
| 210 |
+ var id = event.detail.target.id.replace('wa-checkin-', '');
|
|
| 211 |
+ if (window.modals && window.modals.wa_checkins && window.modals.wa_checkins['details' + id]) {
|
|
| 212 |
+ setTimeout(function() {
|
|
| 213 |
+ window.modals.wa_checkins['details' + id].options.onOpen(); |
|
| 214 |
+ }, 100); |
|
| 215 |
+ } |
|
| 216 |
+ } |
|
| 217 |
+ } |
|
| 218 |
+ }); |
|
| 219 |
+ |
|
| 220 |
+ // Log HTMX request errors |
|
| 221 |
+ document.body.addEventListener('htmx:responseError', function(event) {
|
|
| 222 |
+ console.error('HTMX response error:', event.detail.error, event.detail.xhr);
|
|
| 223 |
+ }); |
|
| 224 |
+ |
|
| 225 |
+ // Log when HTMX sends a request |
|
| 226 |
+ document.body.addEventListener('htmx:beforeRequest', function(event) {
|
|
| 227 |
+ console.log('HTMX sending request to:', event.detail.path);
|
|
| 228 |
+ }); |
|
| 133 | 229 |
|
| 134 | 230 |
window.modals = window.modals || [] |
| 135 | 231 |
window.modals.wa_checkins = window.modals.wa_checkins || [] |
| ... | ... |
@@ -159,28 +255,66 @@ |
| 159 | 255 |
setTimeout(initDynamicSelects, 100); // Small delay to ensure DOM is ready |
| 160 | 256 |
} |
| 161 | 257 |
|
| 162 |
- // Function to initialize the dynamic select behavior |
|
| 258 |
+ // Function to initialize the dynamic input behavior |
|
| 163 | 259 |
function initDynamicSelects() {
|
| 260 |
+ console.log('Initializing dynamic selects for check-in form');
|
|
| 261 |
+ // Store this function in the global scope for reuse after HTMX swaps |
|
| 262 |
+ window.initDynamicSelectsForCheckin = initDynamicSelects; |
|
| 263 |
+ |
|
| 264 |
+ // Reset the numbersLoaded flag if this is being called after an HTMX swap |
|
| 265 |
+ if (window.htmxSwapOccurred) {
|
|
| 266 |
+ console.log('HTMX swap detected, resetting numbersLoaded flag');
|
|
| 267 |
+ window.htmxSwapOccurred = false; |
|
| 268 |
+ numbersLoaded = false; |
|
| 269 |
+ |
|
| 270 |
+ // Force immediate loading of available numbers after HTMX swap |
|
| 271 |
+ setTimeout(function() {
|
|
| 272 |
+ console.log('Forcing load of available numbers after HTMX swap');
|
|
| 273 |
+ loadAvailableNumbers(); |
|
| 274 |
+ }, 200); |
|
| 275 |
+ } |
|
| 164 | 276 |
// Cache DOM elements and data |
| 165 |
- var $selects = $('#wa-checkin-{{ id }} select[name="behaelter_numbers[]"]');
|
|
| 277 |
+ var $inputs = $('#wa-checkin-{{ id }} input[name="behaelter_numbers[]"]');
|
|
| 278 |
+ var $form = $('#wa-checkin-{{ id }} form');
|
|
| 279 |
+ var $datalist = $('#behaelter-numbers-list');
|
|
| 166 | 280 |
var allOptionsArray = []; // Store all available options as a flat array |
| 167 |
- var selectsCount = $selects.length; |
|
| 281 |
+ var inputsCount = $inputs.length; |
|
| 168 | 282 |
var previousValues = {}; // Track previous values to detect changes
|
| 169 |
- var optionElements = {}; // Pre-created option elements for better performance
|
|
| 170 | 283 |
var isLoadingNumbers = false; // Flag to prevent multiple simultaneous requests |
| 171 | 284 |
var numbersLoaded = false; // Flag to track if numbers have been loaded |
| 285 |
+ var validationInProgress = false; // Flag to track if validation is in progress |
|
| 286 |
+ var validationResults = {}; // Store validation results for each input
|
|
| 287 |
+ |
|
| 288 |
+ // Check if there are server-side validation errors |
|
| 289 |
+ var hasServerErrors = $inputs.filter('.is-invalid').length > 0;
|
|
| 290 |
+ |
|
| 291 |
+ // If there are server-side errors, scroll to the first invalid field |
|
| 292 |
+ if (hasServerErrors) {
|
|
| 293 |
+ var $firstInvalid = $inputs.filter('.is-invalid').first();
|
|
| 294 |
+ if ($firstInvalid.length) {
|
|
| 295 |
+ setTimeout(function() {
|
|
| 296 |
+ $firstInvalid[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
| 297 |
+ $firstInvalid.focus(); |
|
| 298 |
+ }, 500); |
|
| 299 |
+ } |
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ // Load available numbers immediately when the modal is opened |
|
| 303 |
+ loadAvailableNumbers(); |
|
| 172 | 304 |
|
| 173 | 305 |
// Function to load available numbers via Ajax |
| 174 | 306 |
function loadAvailableNumbers() {
|
| 307 |
+ console.log('Loading available numbers for check-in form');
|
|
| 175 | 308 |
if (isLoadingNumbers || numbersLoaded) {
|
| 309 |
+ console.log('Numbers already loading or loaded, skipping');
|
|
| 176 | 310 |
return; |
| 177 | 311 |
} |
| 178 | 312 |
|
| 179 | 313 |
isLoadingNumbers = true; |
| 180 | 314 |
|
| 181 | 315 |
// Show loading animation |
| 182 |
- $selects.prop('disabled', true);
|
|
| 183 |
- $selects.first().after('<div id="numbers-loading" class="text-sm text-muted"><div class="loading-spinner"></div> Lade verfügbare Nummern...</div>');
|
|
| 316 |
+ $inputs.prop('disabled', true);
|
|
| 317 |
+ $inputs.first().after('<div id="numbers-loading" class="text-sm text-muted"><div class="loading-spinner"></div> Lade verfügbare Nummern...</div>');
|
|
| 184 | 318 |
|
| 185 | 319 |
// Make Ajax request to get available numbers |
| 186 | 320 |
$.ajax({
|
| ... | ... |
@@ -210,24 +344,30 @@ |
| 210 | 344 |
}; |
| 211 | 345 |
}); |
| 212 | 346 |
|
| 213 |
- // Pre-create option elements for better performance |
|
| 347 |
+ // Clear the datalist |
|
| 348 |
+ $datalist.empty(); |
|
| 349 |
+ |
|
| 350 |
+ // Add options to the datalist |
|
| 214 | 351 |
allOptionsArray.forEach(function(option) {
|
| 215 |
- optionElements[option.value] = $('<option>', {
|
|
| 352 |
+ var $option = $('<option>', {
|
|
| 216 | 353 |
value: option.value, |
| 217 |
- text: option.text, |
|
| 218 | 354 |
class: option.isSpecial ? 'special-option' : '' |
| 219 |
- })[0]; // Get the raw DOM element for better performance |
|
| 220 |
- }); |
|
| 355 |
+ }); |
|
| 221 | 356 |
|
| 222 |
- // Update all selects with the available numbers |
|
| 223 |
- updateSelectOptions(); |
|
| 357 |
+ // For the special option, add a label |
|
| 358 |
+ if (option.isSpecial) {
|
|
| 359 |
+ $option.text('Nummer nicht bekannt');
|
|
| 360 |
+ } |
|
| 224 | 361 |
|
| 225 |
- // Mark all selects as loaded |
|
| 226 |
- $selects.attr('data-loaded', 'true');
|
|
| 362 |
+ $datalist.append($option); |
|
| 363 |
+ }); |
|
| 364 |
+ |
|
| 365 |
+ // Mark all inputs as loaded |
|
| 366 |
+ $inputs.attr('data-loaded', 'true');
|
|
| 227 | 367 |
numbersLoaded = true; |
| 228 | 368 |
} else {
|
| 229 | 369 |
// Handle case where no numbers are available |
| 230 |
- $selects.first().after('<div class="text-sm text-danger">Keine Nummern verfügbar</div>');
|
|
| 370 |
+ $inputs.first().after('<div class="text-sm text-danger">Keine Nummern verfügbar</div>');
|
|
| 231 | 371 |
} |
| 232 | 372 |
}, |
| 233 | 373 |
error: function(xhr) {
|
| ... | ... |
@@ -239,128 +379,341 @@ |
| 239 | 379 |
} |
| 240 | 380 |
} catch (e) {}
|
| 241 | 381 |
|
| 242 |
- $selects.first().after('<div class="text-sm text-danger">' + errorMessage + '</div>');
|
|
| 382 |
+ $inputs.first().after('<div class="text-sm text-danger">' + errorMessage + '</div>');
|
|
| 243 | 383 |
}, |
| 244 | 384 |
complete: function() {
|
| 245 |
- // Remove loading indicator and re-enable selects |
|
| 385 |
+ // Remove loading indicator and re-enable inputs |
|
| 246 | 386 |
$('#numbers-loading').remove();
|
| 247 |
- $selects.prop('disabled', false);
|
|
| 387 |
+ $inputs.prop('disabled', false);
|
|
| 248 | 388 |
isLoadingNumbers = false; |
| 249 | 389 |
} |
| 250 | 390 |
}); |
| 251 | 391 |
} |
| 252 | 392 |
|
| 253 |
- // Function to efficiently update select options |
|
| 254 |
- function updateSelectOptions(changedSelect) {
|
|
| 393 |
+ // Function to update the datalist when input values change |
|
| 394 |
+ function updateDatalist() {
|
|
| 255 | 395 |
// If numbers haven't been loaded yet, don't update |
| 256 | 396 |
if (allOptionsArray.length === 0) {
|
| 257 | 397 |
return; |
| 258 | 398 |
} |
| 259 | 399 |
|
| 260 |
- // Show a brief loading animation when refreshing values |
|
| 261 |
- if (changedSelect) {
|
|
| 262 |
- var $loadingIndicator = $('<div class="loading-spinner" style="position: absolute; right: 25px; top: 50%; transform: translateY(-50%);"></div>');
|
|
| 263 |
- $(changedSelect).parent().css('position', 'relative').append($loadingIndicator);
|
|
| 400 |
+ // Show a brief loading animation |
|
| 401 |
+ var $loadingIndicator = $('<div class="loading-spinner" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999;"></div>');
|
|
| 402 |
+ $('body').append($loadingIndicator);
|
|
| 264 | 403 |
|
| 265 |
- // Remove the loading indicator after a short delay |
|
| 266 |
- setTimeout(function() {
|
|
| 267 |
- $loadingIndicator.remove(); |
|
| 268 |
- }, 500); |
|
| 269 |
- } |
|
| 404 |
+ // Remove the loading indicator after a short delay |
|
| 405 |
+ setTimeout(function() {
|
|
| 406 |
+ $loadingIndicator.remove(); |
|
| 407 |
+ }, 300); |
|
| 270 | 408 |
|
| 271 |
- // Get all currently selected values |
|
| 272 |
- var selectedValues = {};
|
|
| 273 |
- var hasChanges = false; |
|
| 409 |
+ // Get all currently used values |
|
| 410 |
+ var usedValues = {};
|
|
| 274 | 411 |
|
| 275 |
- $selects.each(function() {
|
|
| 412 |
+ $inputs.each(function() {
|
|
| 276 | 413 |
var val = $(this).val(); |
| 277 |
- if (val) {
|
|
| 278 |
- selectedValues[val] = true; |
|
| 414 |
+ if (val && val !== '9999') { // Allow multiple use of special value 9999
|
|
| 415 |
+ usedValues[val] = true; |
|
| 279 | 416 |
} |
| 280 | 417 |
}); |
| 281 | 418 |
|
| 282 |
- // Only update other selects if there's a change |
|
| 283 |
- $selects.each(function() {
|
|
| 284 |
- var $select = $(this); |
|
| 285 |
- var selectId = $select.attr('id');
|
|
| 286 |
- var currentValue = $select.val(); |
|
| 419 |
+ // Clear the datalist |
|
| 420 |
+ $datalist.empty(); |
|
| 287 | 421 |
|
| 288 |
- // Skip the select that triggered the change and selects that don't need updating |
|
| 289 |
- if (changedSelect && this === changedSelect && currentValue) {
|
|
| 290 |
- previousValues[selectId] = currentValue; |
|
| 291 |
- return; |
|
| 292 |
- } |
|
| 422 |
+ // First add the special option (9999) if it's available |
|
| 423 |
+ var specialOption = allOptionsArray.find(function(option) {
|
|
| 424 |
+ return option.isSpecial; |
|
| 425 |
+ }); |
|
| 426 |
+ |
|
| 427 |
+ if (specialOption) {
|
|
| 428 |
+ var $specialOption = $('<option>', {
|
|
| 429 |
+ value: specialOption.value, |
|
| 430 |
+ text: specialOption.text, |
|
| 431 |
+ class: 'special-option' |
|
| 432 |
+ }); |
|
| 433 |
+ $datalist.append($specialOption); |
|
| 434 |
+ } |
|
| 293 | 435 |
|
| 294 |
- // Check if we need to update this select |
|
| 295 |
- var needsUpdate = false; |
|
| 296 |
- if (changedSelect) {
|
|
| 297 |
- needsUpdate = true; // Always update on explicit change |
|
| 298 |
- } else if (!previousValues[selectId] || previousValues[selectId] !== currentValue) {
|
|
| 299 |
- needsUpdate = true; |
|
| 436 |
+ // Then add all other available options |
|
| 437 |
+ allOptionsArray.forEach(function(option) {
|
|
| 438 |
+ // Skip the special option (already added) and used values |
|
| 439 |
+ if (!option.isSpecial && !usedValues[option.value]) {
|
|
| 440 |
+ var $option = $('<option>', {
|
|
| 441 |
+ value: option.value |
|
| 442 |
+ }); |
|
| 443 |
+ $datalist.append($option); |
|
| 300 | 444 |
} |
| 445 |
+ }); |
|
| 446 |
+ } |
|
| 301 | 447 |
|
| 302 |
- if (needsUpdate) {
|
|
| 303 |
- // Store current selection |
|
| 304 |
- var currentSelection = currentValue; |
|
| 448 |
+ // Function to validate a single input field |
|
| 449 |
+ function validateInput($input) {
|
|
| 450 |
+ var inputId = $input.attr('id');
|
|
| 451 |
+ var value = $input.val().trim(); |
|
| 305 | 452 |
|
| 306 |
- // Efficiently update options |
|
| 307 |
- var optionsFragment = document.createDocumentFragment(); |
|
| 308 |
- var emptyOption = $select.find('option:first').clone()[0];
|
|
| 309 |
- optionsFragment.appendChild(emptyOption); |
|
| 453 |
+ // Remove any existing validation message |
|
| 454 |
+ $('#' + inputId + '-validation').remove();
|
|
| 310 | 455 |
|
| 311 |
- // First add the special option (9999) if it's available |
|
| 312 |
- var specialOption = null; |
|
| 313 |
- var regularOptions = []; |
|
| 456 |
+ // Reset validation classes |
|
| 457 |
+ $input.removeClass('is-valid is-invalid');
|
|
| 314 | 458 |
|
| 315 |
- // Separate special option from regular options |
|
| 316 |
- allOptionsArray.forEach(function(option) {
|
|
| 317 |
- // Always include the special option (9999) or options that are either the current selection or not selected elsewhere |
|
| 318 |
- if (option.isSpecial || option.value === currentSelection || !selectedValues[option.value]) {
|
|
| 319 |
- if (option.isSpecial) {
|
|
| 320 |
- specialOption = option; |
|
| 321 |
- } else {
|
|
| 322 |
- regularOptions.push(option); |
|
| 323 |
- } |
|
| 324 |
- } |
|
| 325 |
- }); |
|
| 459 |
+ // If empty, mark as invalid but don't show message (HTML5 required will handle this) |
|
| 460 |
+ if (!value) {
|
|
| 461 |
+ return Promise.resolve(false); |
|
| 462 |
+ } |
|
| 463 |
+ |
|
| 464 |
+ // Check for duplicate values (except for special value 9999) |
|
| 465 |
+ if (value !== '9999') {
|
|
| 466 |
+ var duplicateFound = false; |
|
| 467 |
+ var duplicateIndex = -1; |
|
| 326 | 468 |
|
| 327 |
- // Add special option first (after empty option) |
|
| 328 |
- if (specialOption) {
|
|
| 329 |
- var specialOptionClone = optionElements[specialOption.value].cloneNode(true); |
|
| 330 |
- optionsFragment.appendChild(specialOptionClone); |
|
| 469 |
+ $inputs.each(function(index) {
|
|
| 470 |
+ var $otherInput = $(this); |
|
| 471 |
+ // Skip the current input |
|
| 472 |
+ if ($otherInput.attr('id') === inputId) {
|
|
| 473 |
+ return; |
|
| 331 | 474 |
} |
| 332 | 475 |
|
| 333 |
- // Then add all other options |
|
| 334 |
- regularOptions.forEach(function(option) {
|
|
| 335 |
- var optionClone = optionElements[option.value].cloneNode(true); |
|
| 336 |
- optionsFragment.appendChild(optionClone); |
|
| 337 |
- }); |
|
| 476 |
+ var otherValue = $otherInput.val().trim(); |
|
| 477 |
+ if (otherValue === value) {
|
|
| 478 |
+ duplicateFound = true; |
|
| 479 |
+ duplicateIndex = index + 1; // +1 because index is zero-based |
|
| 480 |
+ return false; // Break the loop |
|
| 481 |
+ } |
|
| 482 |
+ }); |
|
| 338 | 483 |
|
| 339 |
- // Replace all options at once for better performance |
|
| 340 |
- $select.empty()[0].appendChild(optionsFragment); |
|
| 484 |
+ if (duplicateFound) {
|
|
| 485 |
+ $input.addClass('is-invalid');
|
|
| 486 |
+ var $fieldset = $input.closest('fieldset');
|
|
| 487 |
+ $fieldset.append('<div id="' + inputId + '-validation" class="validation-message error">Diese Nummer wird bereits für Behälter ' + duplicateIndex + ' verwendet.</div>');
|
|
| 341 | 488 |
|
| 342 |
- // Restore current selection |
|
| 343 |
- if (currentSelection) {
|
|
| 344 |
- $select.val(currentSelection); |
|
| 345 |
- } |
|
| 489 |
+ // Store validation result |
|
| 490 |
+ validationResults[inputId] = false; |
|
| 491 |
+ return Promise.resolve(false); |
|
| 492 |
+ } |
|
| 493 |
+ } |
|
| 346 | 494 |
|
| 347 |
- previousValues[selectId] = currentSelection; |
|
| 495 |
+ // Show loading indicator |
|
| 496 |
+ var $fieldset = $input.closest('fieldset');
|
|
| 497 |
+ var $loadingIndicator = $('<div class="loading-spinner" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%);"></div>');
|
|
| 498 |
+ $fieldset.css('position', 'relative').append($loadingIndicator);
|
|
| 499 |
+ |
|
| 500 |
+ // Make Ajax request to validate the number |
|
| 501 |
+ return $.ajax({
|
|
| 502 |
+ url: '/_ajax/vr_wa/v1/slot', |
|
| 503 |
+ method: 'GET', |
|
| 504 |
+ data: {
|
|
| 505 |
+ do: 'validateNumber', |
|
| 506 |
+ id: '{{ id }}',
|
|
| 507 |
+ number: value |
|
| 508 |
+ }, |
|
| 509 |
+ dataType: 'json' |
|
| 510 |
+ }).then(function(response) {
|
|
| 511 |
+ // Remove loading indicator |
|
| 512 |
+ $loadingIndicator.remove(); |
|
| 513 |
+ |
|
| 514 |
+ // Store validation result |
|
| 515 |
+ validationResults[inputId] = response.valid; |
|
| 516 |
+ |
|
| 517 |
+ if (response.valid) {
|
|
| 518 |
+ // Valid number |
|
| 519 |
+ $input.addClass('is-valid');
|
|
| 520 |
+ return true; |
|
| 521 |
+ } else {
|
|
| 522 |
+ // Invalid number |
|
| 523 |
+ $input.addClass('is-invalid');
|
|
| 524 |
+ |
|
| 525 |
+ // Add validation message |
|
| 526 |
+ var message = response.message || 'Ungültige Nummer'; |
|
| 527 |
+ $fieldset.append('<div id="' + inputId + '-validation" class="validation-message error">' + message + '</div>');
|
|
| 528 |
+ |
|
| 529 |
+ return false; |
|
| 348 | 530 |
} |
| 531 |
+ }).catch(function() {
|
|
| 532 |
+ // Remove loading indicator |
|
| 533 |
+ $loadingIndicator.remove(); |
|
| 534 |
+ |
|
| 535 |
+ // Mark as invalid on error |
|
| 536 |
+ $input.addClass('is-invalid');
|
|
| 537 |
+ $fieldset.append('<div id="' + inputId + '-validation" class="validation-message error">Fehler bei der Validierung</div>');
|
|
| 538 |
+ |
|
| 539 |
+ // Store validation result |
|
| 540 |
+ validationResults[inputId] = false; |
|
| 541 |
+ |
|
| 542 |
+ return false; |
|
| 543 |
+ }); |
|
| 544 |
+ } |
|
| 545 |
+ |
|
| 546 |
+ // Function to validate all inputs |
|
| 547 |
+ function validateAllInputs() {
|
|
| 548 |
+ if (validationInProgress) {
|
|
| 549 |
+ return Promise.reject('Validation already in progress');
|
|
| 550 |
+ } |
|
| 551 |
+ |
|
| 552 |
+ validationInProgress = true; |
|
| 553 |
+ |
|
| 554 |
+ // Disable submit button during validation |
|
| 555 |
+ $('#checkin-submit').prop('disabled', true).html('<div class="loading-spinner" style="display: inline-block;"></div> Validiere...');
|
|
| 556 |
+ |
|
| 557 |
+ // Create an array of promises for each input validation |
|
| 558 |
+ var validationPromises = []; |
|
| 559 |
+ |
|
| 560 |
+ $inputs.each(function() {
|
|
| 561 |
+ var $input = $(this); |
|
| 562 |
+ validationPromises.push(validateInput($input)); |
|
| 563 |
+ }); |
|
| 564 |
+ |
|
| 565 |
+ // Wait for all validations to complete |
|
| 566 |
+ return Promise.all(validationPromises).then(function(results) {
|
|
| 567 |
+ // Check if all inputs are valid |
|
| 568 |
+ var allValid = results.every(function(result) {
|
|
| 569 |
+ return result === true; |
|
| 570 |
+ }); |
|
| 571 |
+ |
|
| 572 |
+ // Re-enable submit button |
|
| 573 |
+ $('#checkin-submit').prop('disabled', false).text('Check-in durchführen');
|
|
| 574 |
+ |
|
| 575 |
+ validationInProgress = false; |
|
| 576 |
+ return allValid; |
|
| 577 |
+ }).catch(function(error) {
|
|
| 578 |
+ console.error('Validation error:', error);
|
|
| 579 |
+ |
|
| 580 |
+ // Re-enable submit button |
|
| 581 |
+ $('#checkin-submit').prop('disabled', false).text('Check-in durchführen');
|
|
| 582 |
+ |
|
| 583 |
+ validationInProgress = false; |
|
| 584 |
+ return false; |
|
| 349 | 585 |
}); |
| 350 | 586 |
} |
| 351 | 587 |
|
| 352 |
- // Add change event to all selects |
|
| 353 |
- $selects.on('change', function() {
|
|
| 354 |
- updateSelectOptions(this); |
|
| 588 |
+ // Add change event to all inputs |
|
| 589 |
+ $inputs.on('change', function() {
|
|
| 590 |
+ updateDatalist(); |
|
| 355 | 591 |
}); |
| 356 | 592 |
|
| 357 |
- // Add focus event to all selects to load numbers when first focused |
|
| 358 |
- $selects.on('focus', function() {
|
|
| 593 |
+ // Add focus event to all inputs to load numbers when first focused |
|
| 594 |
+ $inputs.on('focus', function() {
|
|
| 359 | 595 |
if (!numbersLoaded) {
|
| 360 | 596 |
loadAvailableNumbers(); |
| 361 | 597 |
} |
| 362 | 598 |
}); |
| 363 | 599 |
|
| 600 |
+ // Add input event to handle typing in the fields |
|
| 601 |
+ $inputs.on('input', function() {
|
|
| 602 |
+ // Update the datalist when the user types in the field |
|
| 603 |
+ // Use a small delay to avoid too frequent updates |
|
| 604 |
+ clearTimeout($(this).data('inputTimer'));
|
|
| 605 |
+ $(this).data('inputTimer', setTimeout(updateDatalist, 300));
|
|
| 606 |
+ |
|
| 607 |
+ // Only remove validation classes and messages if this is not a server-side error |
|
| 608 |
+ // or if the user has changed the value |
|
| 609 |
+ var $input = $(this); |
|
| 610 |
+ var inputId = $input.attr('id');
|
|
| 611 |
+ var initialValue = $input.data('initial-value');
|
|
| 612 |
+ var currentValue = $input.val(); |
|
| 613 |
+ |
|
| 614 |
+ // Store initial value on first input |
|
| 615 |
+ if (initialValue === undefined) {
|
|
| 616 |
+ $input.data('initial-value', currentValue);
|
|
| 617 |
+ initialValue = currentValue; |
|
| 618 |
+ } |
|
| 619 |
+ |
|
| 620 |
+ // If value has changed from initial value, remove validation classes |
|
| 621 |
+ if (currentValue !== initialValue) {
|
|
| 622 |
+ $input.removeClass('is-valid is-invalid');
|
|
| 623 |
+ $('#' + inputId + '-validation').remove();
|
|
| 624 |
+ } |
|
| 625 |
+ }); |
|
| 626 |
+ |
|
| 627 |
+ // Add blur event to validate when field loses focus |
|
| 628 |
+ $inputs.on('blur', function() {
|
|
| 629 |
+ var $input = $(this); |
|
| 630 |
+ var value = $input.val().trim(); |
|
| 631 |
+ |
|
| 632 |
+ // Only validate if the field has a value |
|
| 633 |
+ if (value) {
|
|
| 634 |
+ validateInput($input); |
|
| 635 |
+ } |
|
| 636 |
+ }); |
|
| 637 |
+ |
|
| 638 |
+ // Add form submit handler to validate all fields before submission |
|
| 639 |
+ $form.on('submit', function(e) {
|
|
| 640 |
+ // Prevent the default form submission |
|
| 641 |
+ e.preventDefault(); |
|
| 642 |
+ |
|
| 643 |
+ // Remove any existing toast messages |
|
| 644 |
+ $form.find('.toast').remove();
|
|
| 645 |
+ |
|
| 646 |
+ // Validate all inputs |
|
| 647 |
+ validateAllInputs().then(function(isValid) {
|
|
| 648 |
+ if (isValid) {
|
|
| 649 |
+ // If all inputs are valid, submit the form using HTMX |
|
| 650 |
+ // Show loading state on submit button |
|
| 651 |
+ $('#checkin-submit').prop('disabled', true).html('<div class="loading-spinner" style="display: inline-block;"></div> Wird gespeichert...');
|
|
| 652 |
+ |
|
| 653 |
+ // Use HTMX to submit the form |
|
| 654 |
+ var formData = new FormData($form[0]); |
|
| 655 |
+ var url = $form.attr('hx-post') || $form.attr('action');
|
|
| 656 |
+ var target = $form.closest('[hx-target]').attr('hx-target');
|
|
| 657 |
+ var swap = $form.closest('[hx-swap]').attr('hx-swap');
|
|
| 658 |
+ |
|
| 659 |
+ console.log('Submitting form via HTMX to:', url);
|
|
| 660 |
+ console.log('Target:', target);
|
|
| 661 |
+ console.log('Swap:', swap);
|
|
| 662 |
+ |
|
| 663 |
+ try {
|
|
| 664 |
+ // Check if HTMX is available |
|
| 665 |
+ if (typeof htmx !== 'undefined') {
|
|
| 666 |
+ htmx.ajax('POST', url, {
|
|
| 667 |
+ target: target, |
|
| 668 |
+ swap: swap, |
|
| 669 |
+ values: formData, |
|
| 670 |
+ headers: {
|
|
| 671 |
+ 'HX-Request': 'true' |
|
| 672 |
+ }, |
|
| 673 |
+ error: function(xhr, status) {
|
|
| 674 |
+ console.error('HTMX form submission error:', status);
|
|
| 675 |
+ $('#checkin-submit').prop('disabled', false).text('Check-in durchführen');
|
|
| 676 |
+ var $toast = $('<div class="toast toast--danger mx-0 mb-3"><p>Es ist ein Fehler bei der Übermittlung aufgetreten. Bitte versuchen Sie es erneut.</p></div>');
|
|
| 677 |
+ $form.prepend($toast); |
|
| 678 |
+ } |
|
| 679 |
+ }); |
|
| 680 |
+ } else {
|
|
| 681 |
+ // Fallback to standard form submission if HTMX is not available |
|
| 682 |
+ console.warn('HTMX not available, falling back to standard form submission');
|
|
| 683 |
+ $form[0].submit(); |
|
| 684 |
+ } |
|
| 685 |
+ } catch (e) {
|
|
| 686 |
+ console.error('HTMX form submission exception:', e);
|
|
| 687 |
+ $('#checkin-submit').prop('disabled', false).text('Check-in durchführen');
|
|
| 688 |
+ var $toast = $('<div class="toast toast--danger mx-0 mb-3"><p>Es ist ein Fehler bei der Übermittlung aufgetreten. Bitte versuchen Sie es erneut.</p></div>');
|
|
| 689 |
+ $form.prepend($toast); |
|
| 690 |
+ } |
|
| 691 |
+ } else {
|
|
| 692 |
+ // If any input is invalid, show a message |
|
| 693 |
+ var $toast = $('<div class="toast toast--danger mx-0 mb-3"><p>Bitte korrigieren Sie die markierten Felder.</p></div>');
|
|
| 694 |
+ $form.prepend($toast); |
|
| 695 |
+ |
|
| 696 |
+ // Scroll to the first invalid input |
|
| 697 |
+ var $firstInvalid = $inputs.filter('.is-invalid').first();
|
|
| 698 |
+ if ($firstInvalid.length) {
|
|
| 699 |
+ $firstInvalid[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
| 700 |
+ $firstInvalid.focus(); |
|
| 701 |
+ } |
|
| 702 |
+ |
|
| 703 |
+ // Remove the toast after 5 seconds |
|
| 704 |
+ setTimeout(function() {
|
|
| 705 |
+ $toast.fadeOut(function() {
|
|
| 706 |
+ $(this).remove(); |
|
| 707 |
+ }); |
|
| 708 |
+ }, 5000); |
|
| 709 |
+ } |
|
| 710 |
+ }).catch(function(error) {
|
|
| 711 |
+ console.error('Form validation error:', error);
|
|
| 712 |
+ var $toast = $('<div class="toast toast--danger mx-0 mb-3"><p>Es ist ein Fehler bei der Validierung aufgetreten. Bitte versuchen Sie es erneut.</p></div>');
|
|
| 713 |
+ $form.prepend($toast); |
|
| 714 |
+ }); |
|
| 715 |
+ }); |
|
| 716 |
+ |
|
| 364 | 717 |
// Add click handler to the load numbers button |
| 365 | 718 |
$('#load-numbers-btn').on('click', function(e) {
|
| 366 | 719 |
e.preventDefault(); |
| ... | ... |
@@ -112,6 +112,9 @@ class SlotAjaxController extends AbstractController |
| 112 | 112 |
|
| 113 | 113 |
case 'getAvailableNumbers': |
| 114 | 114 |
return $this->getAvailableNumbers(); |
| 115 |
+ |
|
| 116 |
+ case 'validateNumber': |
|
| 117 |
+ return $this->validateNumber(); |
|
| 115 | 118 |
} |
| 116 | 119 |
|
| 117 | 120 |
return new Response('',500);
|
| ... | ... |
@@ -684,7 +687,7 @@ class SlotAjaxController extends AbstractController |
| 684 | 687 |
return $this->render('@Contao/modal_unauthorized.html.twig');
|
| 685 | 688 |
} |
| 686 | 689 |
|
| 687 |
- protected function renderCheckin(bool $blnModal=true, string $error=null) |
|
| 690 |
+ protected function renderCheckin(bool $blnModal=true, string $error=null, array $formData=null) |
|
| 688 | 691 |
{
|
| 689 | 692 |
$insertTagService = Controller::getContainer()->get('contao.insert_tag.parser');
|
| 690 | 693 |
|
| ... | ... |
@@ -770,6 +773,11 @@ class SlotAjaxController extends AbstractController |
| 770 | 773 |
'current_member' => $currentMemberModel ? $currentMemberModel->row() : null |
| 771 | 774 |
]); |
| 772 | 775 |
|
| 776 |
+ // Add form data if provided (to preserve values after validation errors) |
|
| 777 |
+ if ($formData !== null) {
|
|
| 778 |
+ $arrData['form_data'] = $formData; |
|
| 779 |
+ } |
|
| 780 |
+ |
|
| 773 | 781 |
if (!empty($error)) |
| 774 | 782 |
{
|
| 775 | 783 |
$arrData['toast'] = $error; |
| ... | ... |
@@ -795,7 +803,12 @@ class SlotAjaxController extends AbstractController |
| 795 | 803 |
$behaelterNumbers = Input::post('behaelter_numbers');
|
| 796 | 804 |
if (!is_array($behaelterNumbers) || count($behaelterNumbers) != $Booking->behaelter) |
| 797 | 805 |
{
|
| 798 |
- return $this->renderCheckin(false, '<div class="toast toast--danger mx-0">Bitte wählen Sie für jeden Behälter eine Nummer aus.</div>'); |
|
| 806 |
+ // Prepare form data to preserve input values |
|
| 807 |
+ $formData = [ |
|
| 808 |
+ 'behaelter_numbers' => $behaelterNumbers ?: [], |
|
| 809 |
+ 'member_numbers' => Input::post('member_numbers') ?: []
|
|
| 810 |
+ ]; |
|
| 811 |
+ return $this->renderCheckin(false, '<div class="toast toast--danger mx-0">Bitte wählen Sie für jeden Behälter eine Nummer aus.</div>', $formData); |
|
| 799 | 812 |
} |
| 800 | 813 |
|
| 801 | 814 |
// Get member numbers from the form |
| ... | ... |
@@ -819,7 +832,98 @@ class SlotAjaxController extends AbstractController |
| 819 | 832 |
// Check for duplicate numbers (excluding the special value 9999) |
| 820 | 833 |
if (count(array_unique($numbersForDuplicateCheck)) != count($numbersForDuplicateCheck)) |
| 821 | 834 |
{
|
| 822 |
- return $this->renderCheckin(false, '<div class="toast toast--danger mx-0">Jede Nummer kann nur einmal verwendet werden.</div>'); |
|
| 835 |
+ // Prepare form data to preserve input values |
|
| 836 |
+ $formData = [ |
|
| 837 |
+ 'behaelter_numbers' => $behaelterNumbers, |
|
| 838 |
+ 'member_numbers' => $memberNumbers |
|
| 839 |
+ ]; |
|
| 840 |
+ return $this->renderCheckin(false, '<div class="toast toast--danger mx-0">Jede Nummer kann nur einmal verwendet werden.</div>', $formData); |
|
| 841 |
+ } |
|
| 842 |
+ |
|
| 843 |
+ // Validate all numbers on the server side as a final check |
|
| 844 |
+ $invalidNumbers = []; |
|
| 845 |
+ $currentTime = time(); |
|
| 846 |
+ $Slot = $Booking->getRelated('pid');
|
|
| 847 |
+ $Standort = $Slot->getRelated('pid');
|
|
| 848 |
+ |
|
| 849 |
+ // Get all used numbers from current bookings (excluding past bookings) |
|
| 850 |
+ $usedNumbers = []; |
|
| 851 |
+ |
|
| 852 |
+ // Get the database connection |
|
| 853 |
+ $db = Controller::getContainer()->get('database_connection');
|
|
| 854 |
+ |
|
| 855 |
+ // Query to get used numbers from current bookings |
|
| 856 |
+ $sql = "SELECT r.behaelter_numbers |
|
| 857 |
+ FROM tl_vr_wa_reservation r |
|
| 858 |
+ JOIN tl_vr_wa_slot s ON r.pid = s.id |
|
| 859 |
+ WHERE r.behaelter_numbers != '' |
|
| 860 |
+ AND s.time >= ? |
|
| 861 |
+ AND r.id != ? |
|
| 862 |
+ AND s.pid = ?"; // Only check for the same standort |
|
| 863 |
+ |
|
| 864 |
+ $stmt = $db->prepare($sql); |
|
| 865 |
+ $stmt->bindValue(1, $currentTime); |
|
| 866 |
+ $stmt->bindValue(2, $Booking->id); |
|
| 867 |
+ $stmt->bindValue(3, $Standort->id); |
|
| 868 |
+ $result = $stmt->executeQuery(); |
|
| 869 |
+ |
|
| 870 |
+ while ($row = $result->fetchAssociative()) {
|
|
| 871 |
+ $numbers = json_decode($row['behaelter_numbers'], true); |
|
| 872 |
+ if (is_array($numbers)) {
|
|
| 873 |
+ foreach ($numbers as $item) {
|
|
| 874 |
+ $usedNumbers[] = isset($item['behaelter']) ? $item['behaelter'] : $item; |
|
| 875 |
+ } |
|
| 876 |
+ } |
|
| 877 |
+ } |
|
| 878 |
+ |
|
| 879 |
+ // Check each number |
|
| 880 |
+ foreach ($behaelterNumbers as $index => $number) {
|
|
| 881 |
+ // Skip the special value 9999 |
|
| 882 |
+ if ($number === '9999') {
|
|
| 883 |
+ continue; |
|
| 884 |
+ } |
|
| 885 |
+ |
|
| 886 |
+ // Check if the number is numeric |
|
| 887 |
+ if (!is_numeric($number)) {
|
|
| 888 |
+ $invalidNumbers[] = "Behälter " . ($index + 1) . ": Die eingegebene Nummer ist keine gültige Zahl."; |
|
| 889 |
+ continue; |
|
| 890 |
+ } |
|
| 891 |
+ |
|
| 892 |
+ // Check if the number is already in use |
|
| 893 |
+ if (in_array($number, $usedNumbers)) {
|
|
| 894 |
+ $invalidNumbers[] = "Behälter " . ($index + 1) . ": Diese Nummer wird bereits in einer anderen aktiven Buchung verwendet."; |
|
| 895 |
+ continue; |
|
| 896 |
+ } |
|
| 897 |
+ |
|
| 898 |
+ // Check if the number is within the valid ranges for this standort |
|
| 899 |
+ $validRanges = $Standort->extractNumbersFromRanges([], 10000); // Get all possible numbers |
|
| 900 |
+ if (!in_array($number, $validRanges) && !empty($validRanges)) {
|
|
| 901 |
+ $invalidNumbers[] = "Behälter " . ($index + 1) . ": Die eingegebene Nummer liegt nicht im gültigen Bereich für diesen Standort."; |
|
| 902 |
+ } |
|
| 903 |
+ } |
|
| 904 |
+ |
|
| 905 |
+ // If there are invalid numbers, return an error |
|
| 906 |
+ if (!empty($invalidNumbers)) {
|
|
| 907 |
+ $errorMessage = '<div class="toast toast--danger mx-0"><p>Folgende Fehler wurden gefunden:</p><ul>'; |
|
| 908 |
+ foreach ($invalidNumbers as $error) {
|
|
| 909 |
+ $errorMessage .= '<li>' . $error . '</li>'; |
|
| 910 |
+ } |
|
| 911 |
+ $errorMessage .= '</ul></div>'; |
|
| 912 |
+ |
|
| 913 |
+ // Prepare form data to preserve input values |
|
| 914 |
+ $formData = [ |
|
| 915 |
+ 'behaelter_numbers' => $behaelterNumbers, |
|
| 916 |
+ 'member_numbers' => $memberNumbers, |
|
| 917 |
+ 'invalid_fields' => array_map(function($error) {
|
|
| 918 |
+ // Extract the behälter number from the error message |
|
| 919 |
+ if (preg_match('/Behälter (\d+):/', $error, $matches)) {
|
|
| 920 |
+ return (int)$matches[1] - 1; // Convert to zero-based index |
|
| 921 |
+ } |
|
| 922 |
+ return null; |
|
| 923 |
+ }, $invalidNumbers) |
|
| 924 |
+ ]; |
|
| 925 |
+ |
|
| 926 |
+ return $this->renderCheckin(false, $errorMessage, $formData); |
|
| 823 | 927 |
} |
| 824 | 928 |
|
| 825 | 929 |
// Create combined array with behaelter numbers and member numbers |
| ... | ... |
@@ -926,4 +1030,91 @@ class SlotAjaxController extends AbstractController |
| 926 | 1030 |
// Return the numbers as JSON |
| 927 | 1031 |
return new Response(json_encode(['numbers' => $availableNumbers]), 200, ['Content-Type' => 'application/json']); |
| 928 | 1032 |
} |
| 1033 |
+ |
|
| 1034 |
+ protected function validateNumber() |
|
| 1035 |
+ {
|
|
| 1036 |
+ if (empty($_REQUEST['id']) || !isset($_REQUEST['number'])) |
|
| 1037 |
+ {
|
|
| 1038 |
+ return new Response(json_encode(['valid' => false, 'message' => 'Required parameters missing']), 412, ['Content-Type' => 'application/json']); |
|
| 1039 |
+ } |
|
| 1040 |
+ |
|
| 1041 |
+ $number = $_REQUEST['number']; |
|
| 1042 |
+ |
|
| 1043 |
+ // Special case: "Nummer nicht bekannt" is always valid |
|
| 1044 |
+ if ($number === '9999') {
|
|
| 1045 |
+ return new Response(json_encode(['valid' => true]), 200, ['Content-Type' => 'application/json']); |
|
| 1046 |
+ } |
|
| 1047 |
+ |
|
| 1048 |
+ // Check if the number is a valid number format |
|
| 1049 |
+ if (!is_numeric($number)) {
|
|
| 1050 |
+ return new Response(json_encode([ |
|
| 1051 |
+ 'valid' => false, |
|
| 1052 |
+ 'message' => 'Die eingegebene Nummer ist keine gültige Zahl.' |
|
| 1053 |
+ ]), 200, ['Content-Type' => 'application/json']); |
|
| 1054 |
+ } |
|
| 1055 |
+ |
|
| 1056 |
+ /** @var WeinanlieferungReservationModel $Booking */ |
|
| 1057 |
+ if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null)
|
|
| 1058 |
+ {
|
|
| 1059 |
+ return new Response(json_encode(['valid' => false, 'message' => 'Could not load booking data']), 500, ['Content-Type' => 'application/json']); |
|
| 1060 |
+ } |
|
| 1061 |
+ |
|
| 1062 |
+ // Get the standort to access the number_ranges |
|
| 1063 |
+ $Standort = $Slot->getRelated('pid');
|
|
| 1064 |
+ if ($Standort === null) |
|
| 1065 |
+ {
|
|
| 1066 |
+ return new Response(json_encode(['valid' => false, 'message' => 'Could not load standort data']), 500, ['Content-Type' => 'application/json']); |
|
| 1067 |
+ } |
|
| 1068 |
+ |
|
| 1069 |
+ // Get all used numbers from current bookings (excluding past bookings) |
|
| 1070 |
+ $usedNumbers = []; |
|
| 1071 |
+ $currentTime = time(); |
|
| 1072 |
+ |
|
| 1073 |
+ // Get the database connection |
|
| 1074 |
+ $db = Controller::getContainer()->get('database_connection');
|
|
| 1075 |
+ |
|
| 1076 |
+ // Query to get used numbers from current bookings |
|
| 1077 |
+ $sql = "SELECT r.behaelter_numbers |
|
| 1078 |
+ FROM tl_vr_wa_reservation r |
|
| 1079 |
+ JOIN tl_vr_wa_slot s ON r.pid = s.id |
|
| 1080 |
+ WHERE r.behaelter_numbers != '' |
|
| 1081 |
+ AND s.time >= ? |
|
| 1082 |
+ AND r.id != ? |
|
| 1083 |
+ AND s.pid = ?"; // Only check for the same standort |
|
| 1084 |
+ |
|
| 1085 |
+ $stmt = $db->prepare($sql); |
|
| 1086 |
+ $stmt->bindValue(1, $currentTime); |
|
| 1087 |
+ $stmt->bindValue(2, $Booking->id); |
|
| 1088 |
+ $stmt->bindValue(3, $Standort->id); |
|
| 1089 |
+ $result = $stmt->executeQuery(); |
|
| 1090 |
+ |
|
| 1091 |
+ while ($row = $result->fetchAssociative()) {
|
|
| 1092 |
+ $numbers = json_decode($row['behaelter_numbers'], true); |
|
| 1093 |
+ if (is_array($numbers)) {
|
|
| 1094 |
+ foreach ($numbers as $item) {
|
|
| 1095 |
+ $usedNumbers[] = isset($item['behaelter']) ? $item['behaelter'] : $item; |
|
| 1096 |
+ } |
|
| 1097 |
+ } |
|
| 1098 |
+ } |
|
| 1099 |
+ |
|
| 1100 |
+ // Check if the number is already in use |
|
| 1101 |
+ if (in_array($number, $usedNumbers)) {
|
|
| 1102 |
+ return new Response(json_encode([ |
|
| 1103 |
+ 'valid' => false, |
|
| 1104 |
+ 'message' => 'Diese Nummer wird bereits in einer anderen aktiven Buchung verwendet.' |
|
| 1105 |
+ ]), 200, ['Content-Type' => 'application/json']); |
|
| 1106 |
+ } |
|
| 1107 |
+ |
|
| 1108 |
+ // Check if the number is within the valid ranges for this standort |
|
| 1109 |
+ $validRanges = $Standort->extractNumbersFromRanges([], 10000); // Get all possible numbers |
|
| 1110 |
+ if (!in_array($number, $validRanges) && !empty($validRanges)) {
|
|
| 1111 |
+ return new Response(json_encode([ |
|
| 1112 |
+ 'valid' => false, |
|
| 1113 |
+ 'message' => 'Die eingegebene Nummer liegt nicht im gültigen Bereich für diesen Standort.' |
|
| 1114 |
+ ]), 200, ['Content-Type' => 'application/json']); |
|
| 1115 |
+ } |
|
| 1116 |
+ |
|
| 1117 |
+ // If we got here, the number is valid |
|
| 1118 |
+ return new Response(json_encode(['valid' => true]), 200, ['Content-Type' => 'application/json']); |
|
| 1119 |
+ } |
|
| 929 | 1120 |
} |