Browse code

Add missing check-in modal window

Benjamin Roth authored on25/07/2025 14:21:15
Showing1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,385 @@
1
+{% block content %}
2
+    {% if modal %}<div class="modal-content">{% endif %}
3
+    <div id="wa-checkin-{{ id }}" hx-target="this" hx-swap="outerHTML" class="frame">
4
+        <div class="frame__header">
5
+            <h3>Check-in</h3>
6
+            <div class="grid-md u-gap-2">
7
+                <div class="grid-c-6 mb-2 mb-0-md">
8
+                    <div class="u-flex u-items-center u-gap-1">
9
+                        <i class="icon-uhr-outline"></i>
10
+                        <div class="t-label">Tag/Urzeit</div>
11
+                    </div>
12
+                    <div class="">{{ slot.time|date('d.m.Y H:i') }}</div>
13
+                </div>
14
+
15
+                <div class="grid-c-6 mb-2 mb-0-md">
16
+                    <div class="u-flex u-items-center u-gap-1">
17
+                        <i class="icon-standort-outline"></i>
18
+                        <div class="t-label">Standort</div>
19
+                    </div>
20
+                    <div class="">{{ standort.title }}</div>
21
+                </div>
22
+
23
+                <div class="grid-c-6 mb-2 mb-0-md">
24
+                    <div class="u-flex u-items-center u-gap-1">
25
+                        <i class="icon-behaelter-outline"></i>
26
+                        <div class="t-label">Gebuchte Behälterkapazität</div>
27
+                    </div>
28
+                    <div class="">{{ buchung.behaelter }}</div>
29
+                </div>
30
+
31
+                <div class="grid-c-6 mb-2 mb-0-md">
32
+                    <div class="u-flex u-items-center u-gap-1">
33
+                        <i class="icon-reben-outline"></i>
34
+                        <div class="t-label">Anliefernde Sorten</div>
35
+                    </div>
36
+                    <div class="">{{ buchung.sorten|join(', ') }}</div>
37
+                </div>
38
+
39
+                {% if slot.anmerkungen %}
40
+                    <div class="grid-c-12 mb-2 mb-0-md">
41
+                        <div class="u-flex u-items-center u-gap-1">
42
+                            <i class="icon-info-outline"></i>
43
+                            <div class="t-label">Anmerkungen</div>
44
+                        </div>
45
+                        {{ slot.anmerkungen|raw }}
46
+                    </div>
47
+                {% endif %}
48
+            </div>
49
+        </div>
50
+
51
+        <div class="divider m-0 mb-2"></div>
52
+
53
+        <div class="frame__body">
54
+            <h3>Behälternummern zuweisen</h3>
55
+            {% if toast is defined %}
56
+                {{ toast|raw }}
57
+            {% endif %}
58
+            <form hx-post="/_ajax/vr_wa/v1/slot?do=updateCheckin" enctype="multipart/form-data">
59
+                <input type="hidden" name="id" value="{{ id }}">
60
+
61
+                <button type="button" id="load-numbers-btn" class="btn btn--sm btn-info mb-2">Nummern laden</button>
62
+
63
+                <div class="grid-md u-gap-2">
64
+                    {% for i in 1..checkin.behaelter %}
65
+                        <div class="grid-c-6">
66
+                            <fieldset>
67
+                                <label for="behaelter-number-{{ i }}"><strong>Behälter {{ i }}</strong><sup class="text-danger">*</sup></label>
68
+                                <select id="behaelter-number-{{ i }}" name="behaelter_numbers[]" required data-loaded="false">
69
+                                    <option value="">-</option>
70
+                                    <!-- Options will be loaded via Ajax when the select is focused -->
71
+                                </select>
72
+                            </fieldset>
73
+                        </div>
74
+                    {% endfor %}
75
+                </div>
76
+
77
+                <fieldset>
78
+                    <button id="checkin-submit" type="submit">Check-in durchführen</button>
79
+                </fieldset>
80
+            </form>
81
+        </div>
82
+    </div>
83
+    {% if modal %}</div>{% endif %}
84
+{% endblock %}
85
+
86
+{% block modal %}
87
+    {% if modal %}
88
+        <style>
89
+            .loading-spinner {
90
+                display: inline-block;
91
+                width: 16px;
92
+                height: 16px;
93
+                border: 2px solid rgba(0, 0, 0, 0.1);
94
+                border-left-color: #3498db;
95
+                border-radius: 50%;
96
+                animation: spin 1s linear infinite;
97
+                vertical-align: middle;
98
+                margin-right: 5px;
99
+            }
100
+
101
+            @keyframes spin {
102
+                0% { transform: rotate(0deg); }
103
+                100% { transform: rotate(360deg); }
104
+            }
105
+
106
+            #numbers-loading {
107
+                display: flex;
108
+                align-items: center;
109
+                margin: 10px 0;
110
+            }
111
+
112
+            /* Style for the special option "Nummer nicht bekannt" */
113
+            .special-option {
114
+                font-weight: bold;
115
+                color: #3498db;
116
+                border-bottom: 1px dashed #ccc;
117
+                margin-bottom: 5px;
118
+            }
119
+        </style>
120
+        <script>
121
+            (function ($) {
122
+
123
+                window.modals = window.modals || []
124
+                window.modals.wa_checkins = window.modals.wa_checkins || []
125
+
126
+                if (window.modals.wa_checkins.details{{ id }} === undefined)
127
+                {
128
+                    window.modals.wa_checkins.details{{ id }} = new jBox('Modal', {
129
+                    closeButton: 'box',
130
+                    content: $('#wa-checkin-{{ id }}'),
131
+                    maxWidth: 650,
132
+                    minWidth: 100,
133
+                    minHeight: 100,
134
+                    width: 650,
135
+                    overlay: true,
136
+                    closeOnClick: false,
137
+                    zIndex: 'auto',
138
+                    addClass: '',
139
+                    onOpen: function() {
140
+                        // Initialize the dynamic select behavior
141
+                        initDynamicSelects();
142
+                    }
143
+                }).open();
144
+                } else {
145
+                    window.modals.wa_checkins.details{{ id }}.content.empty();
146
+                    window.modals.wa_checkins.details{{ id }}.setContent($('#wa-checkin-{{ id }}')).open();
147
+                    // Initialize the dynamic select behavior
148
+                    setTimeout(initDynamicSelects, 100); // Small delay to ensure DOM is ready
149
+                }
150
+
151
+                // Function to initialize the dynamic select behavior
152
+                function initDynamicSelects() {
153
+                    // Cache DOM elements and data
154
+                    var $selects = $('#wa-checkin-{{ id }} select[name="behaelter_numbers[]"]');
155
+                    var allOptionsArray = []; // Store all available options as a flat array
156
+                    var selectsCount = $selects.length;
157
+                    var previousValues = {}; // Track previous values to detect changes
158
+                    var optionElements = {}; // Pre-created option elements for better performance
159
+                    var isLoadingNumbers = false; // Flag to prevent multiple simultaneous requests
160
+                    var numbersLoaded = false; // Flag to track if numbers have been loaded
161
+
162
+                    // Function to load available numbers via Ajax
163
+                    function loadAvailableNumbers() {
164
+                        if (isLoadingNumbers || numbersLoaded) {
165
+                            return;
166
+                        }
167
+
168
+                        isLoadingNumbers = true;
169
+
170
+                        // Show loading animation
171
+                        $selects.prop('disabled', true);
172
+                        $selects.first().after('<div id="numbers-loading" class="text-sm text-muted"><div class="loading-spinner"></div> Lade verfügbare Nummern...</div>');
173
+
174
+                        // Make Ajax request to get available numbers
175
+                        $.ajax({
176
+                            url: '/_ajax/vr_wa/v1/slot',
177
+                            method: 'GET',
178
+                            data: {
179
+                                do: 'getAvailableNumbers',
180
+                                id: '{{ id }}',
181
+                                limit: 500 // Adjust this based on your needs
182
+                            },
183
+                            dataType: 'json',
184
+                            success: function(response) {
185
+                                if (response.numbers && response.numbers.length > 0) {
186
+                                    // Store the numbers in the allOptionsArray
187
+                                    allOptionsArray = response.numbers.map(function(number) {
188
+                                        // Special handling for the value 9999
189
+                                        if (number === '9999') {
190
+                                            return {
191
+                                                value: number,
192
+                                                text: 'Nummer nicht bekannt',
193
+                                                isSpecial: true
194
+                                            };
195
+                                        }
196
+                                        return {
197
+                                            value: number,
198
+                                            text: number
199
+                                        };
200
+                                    });
201
+
202
+                                    // Pre-create option elements for better performance
203
+                                    allOptionsArray.forEach(function(option) {
204
+                                        optionElements[option.value] = $('<option>', {
205
+                                            value: option.value,
206
+                                            text: option.text,
207
+                                            class: option.isSpecial ? 'special-option' : ''
208
+                                        })[0]; // Get the raw DOM element for better performance
209
+                                    });
210
+
211
+                                    // Update all selects with the available numbers
212
+                                    updateSelectOptions();
213
+
214
+                                    // Mark all selects as loaded
215
+                                    $selects.attr('data-loaded', 'true');
216
+                                    numbersLoaded = true;
217
+                                } else {
218
+                                    // Handle case where no numbers are available
219
+                                    $selects.first().after('<div class="text-sm text-danger">Keine Nummern verfügbar</div>');
220
+                                }
221
+                            },
222
+                            error: function(xhr) {
223
+                                var errorMessage = 'Fehler beim Laden der Nummern';
224
+                                try {
225
+                                    var response = JSON.parse(xhr.responseText);
226
+                                    if (response.error) {
227
+                                        errorMessage = response.error;
228
+                                    }
229
+                                } catch (e) {}
230
+
231
+                                $selects.first().after('<div class="text-sm text-danger">' + errorMessage + '</div>');
232
+                            },
233
+                            complete: function() {
234
+                                // Remove loading indicator and re-enable selects
235
+                                $('#numbers-loading').remove();
236
+                                $selects.prop('disabled', false);
237
+                                isLoadingNumbers = false;
238
+                            }
239
+                        });
240
+                    }
241
+
242
+                    // Function to efficiently update select options
243
+                    function updateSelectOptions(changedSelect) {
244
+                        // If numbers haven't been loaded yet, don't update
245
+                        if (allOptionsArray.length === 0) {
246
+                            return;
247
+                        }
248
+
249
+                        // Show a brief loading animation when refreshing values
250
+                        if (changedSelect) {
251
+                            var $loadingIndicator = $('<div class="loading-spinner" style="position: absolute; right: 25px; top: 50%; transform: translateY(-50%);"></div>');
252
+                            $(changedSelect).parent().css('position', 'relative').append($loadingIndicator);
253
+
254
+                            // Remove the loading indicator after a short delay
255
+                            setTimeout(function() {
256
+                                $loadingIndicator.remove();
257
+                            }, 500);
258
+                        }
259
+
260
+                        // Get all currently selected values
261
+                        var selectedValues = {};
262
+                        var hasChanges = false;
263
+
264
+                        $selects.each(function() {
265
+                            var val = $(this).val();
266
+                            if (val) {
267
+                                selectedValues[val] = true;
268
+                            }
269
+                        });
270
+
271
+                        // Only update other selects if there's a change
272
+                        $selects.each(function() {
273
+                            var $select = $(this);
274
+                            var selectId = $select.attr('id');
275
+                            var currentValue = $select.val();
276
+
277
+                            // Skip the select that triggered the change and selects that don't need updating
278
+                            if (changedSelect && this === changedSelect && currentValue) {
279
+                                previousValues[selectId] = currentValue;
280
+                                return;
281
+                            }
282
+
283
+                            // Check if we need to update this select
284
+                            var needsUpdate = false;
285
+                            if (changedSelect) {
286
+                                needsUpdate = true; // Always update on explicit change
287
+                            } else if (!previousValues[selectId] || previousValues[selectId] !== currentValue) {
288
+                                needsUpdate = true;
289
+                            }
290
+
291
+                            if (needsUpdate) {
292
+                                // Store current selection
293
+                                var currentSelection = currentValue;
294
+
295
+                                // Efficiently update options
296
+                                var optionsFragment = document.createDocumentFragment();
297
+                                var emptyOption = $select.find('option:first').clone()[0];
298
+                                optionsFragment.appendChild(emptyOption);
299
+
300
+                                // First add the special option (9999) if it's available
301
+                                var specialOption = null;
302
+                                var regularOptions = [];
303
+
304
+                                // Separate special option from regular options
305
+                                allOptionsArray.forEach(function(option) {
306
+                                    // Always include the special option (9999) or options that are either the current selection or not selected elsewhere
307
+                                    if (option.isSpecial || option.value === currentSelection || !selectedValues[option.value]) {
308
+                                        if (option.isSpecial) {
309
+                                            specialOption = option;
310
+                                        } else {
311
+                                            regularOptions.push(option);
312
+                                        }
313
+                                    }
314
+                                });
315
+
316
+                                // Add special option first (after empty option)
317
+                                if (specialOption) {
318
+                                    var specialOptionClone = optionElements[specialOption.value].cloneNode(true);
319
+                                    optionsFragment.appendChild(specialOptionClone);
320
+                                }
321
+
322
+                                // Then add all other options
323
+                                regularOptions.forEach(function(option) {
324
+                                    var optionClone = optionElements[option.value].cloneNode(true);
325
+                                    optionsFragment.appendChild(optionClone);
326
+                                });
327
+
328
+                                // Replace all options at once for better performance
329
+                                $select.empty()[0].appendChild(optionsFragment);
330
+
331
+                                // Restore current selection
332
+                                if (currentSelection) {
333
+                                    $select.val(currentSelection);
334
+                                }
335
+
336
+                                previousValues[selectId] = currentSelection;
337
+                            }
338
+                        });
339
+                    }
340
+
341
+                    // Add change event to all selects
342
+                    $selects.on('change', function() {
343
+                        updateSelectOptions(this);
344
+                    });
345
+
346
+                    // Add focus event to all selects to load numbers when first focused
347
+                    $selects.on('focus', function() {
348
+                        if (!numbersLoaded) {
349
+                            loadAvailableNumbers();
350
+                        }
351
+                    });
352
+
353
+                    // Add click handler to the load numbers button
354
+                    $('#load-numbers-btn').on('click', function(e) {
355
+                        e.preventDefault();
356
+
357
+                        // Add loading animation to button
358
+                        var $btn = $(this);
359
+                        var originalText = $btn.text();
360
+                        $btn.html('<div class="loading-spinner" style="display: inline-block;"></div> Wird geladen...').prop('disabled', true);
361
+
362
+                        // Store original button state
363
+                        var restoreButton = function() {
364
+                            $btn.html(originalText).prop('disabled', false);
365
+                        };
366
+
367
+                        // Override the complete callback of the Ajax request
368
+                        var originalComplete = $.ajax.prototype.complete;
369
+                        $.ajax.prototype.complete = function() {
370
+                            restoreButton();
371
+                            $.ajax.prototype.complete = originalComplete;
372
+                            return originalComplete.apply(this, arguments);
373
+                        };
374
+
375
+                        loadAvailableNumbers();
376
+
377
+                        // Fallback in case the Ajax request fails or takes too long
378
+                        setTimeout(restoreButton, 10000);
379
+                    });
380
+                }
381
+
382
+            })(jQuery);
383
+        </script>
384
+    {% endif %}
385
+{% endblock %}