PageSourceSearch

https://fire-soul.ru/wp-content/plugins/woo2iiko/core/checkout/cal…/assets/js/calendar-field.js?ver=1.6.1.1

js fire-soul.ru collected 2026-10-04 02:21:51 UTC 15,144 bytes, 351 lines download raw bytes

1/**
2 * Calendar Field - Frontend Logic
3 *
4 * Логика выбора даты и времени доставки на странице чекаута.
5 * Использует REST API для загрузки интервалов времени.
6 *
7 * @since 1.6.0
8 */
9
10(function($) {
11    'use strict';
12
13    /**
14     * Объект Calendar Field
15     */
16    var CalendarField = {
17        /**
18         * Конфигурация из PHP
19         */
20        config: window.iikoCalendarConfig || {},
21
22        /**
23         * Debounce таймер
24         */
25        debounceTimer: null,
26
27        /**
28         * Флаг программного восстановления (не пользовательское изменение)
29         */
30        isRestoring: false,
31
32        /**
33         * Инициализация
34         */
35        init: function() {
36            // Проверка конфигурации
37            if (!this.config.restUrl) {
38                console.error('CalendarField: Config not found!');
39                return;
40            }
41
42            // Привязка событий
43            this.bindEvents();
44
45            // Восстановление даты из localStorage (это триггерит загрузку слотов времени)
46            this.restoreDateFromStorage();
47
48            // Загрузка интервалов времени для текущей даты при первой загрузке
49            // (время будет восстановлено после загрузки слотов в renderTimeSlots)
50            this.loadInitialTimeSlots();
51
52            // console.log('CalendarField: Initialized');
53        },
54
55        /**
56         * Привязка обработчиков событий
57         */
58        bindEvents: function() {
59            var self = this;
60
61            // Изменение даты доставки (поддержка обоих вариантов: #date и #billing_date)
62            $(document).on('change', '#date, #billing_date', function() {
63                var date = $(this).val();
64                
65                // Валидация: проверяем, что дата не пустая и соответствует формату dmY
66                if (!date || date.length !== 8 || isNaN(date)) {
67                    console.error('CalendarField: Invalid date format:', date);
68                    return;
69                }
70                
71                self.saveToStorage('Date', date);
72                
73                // Если это программное восстановление - загружаем слоты БЕЗ сброса времени
74                // Если пользовательское изменение - загружаем слоты СО сбросом времени
75                self.debouncedLoadTimeSlots(date, !self.isRestoring);
76            });
77
78            // Изменение времени доставки (поддержка обоих вариантов: #time и #billing_time)
79            $(document).on('change', '#time, #billing_time', function() {
80                var time = $(this).val();
81                
82                if (time) {
83                    self.saveToStorage('Time', time);
84                }
85            });
86
87            // WooCommerce AJAX обновление чекаута
88            $(document.body).on('updated_checkout', function() {
89                // При обновлении чекаута восстанавливаем только дату
90                // Время будет восстановлено автоматически при загрузке слотов
91                self.restoreDateFromStorage();
92            });
93        },
94
95        /**
96         * Загрузка интервалов времени для даты (с debounce)
97         * @param {string} date - Дата в формате dmY
98         * @param {boolean} resetTime - Сбросить время (true) или попытаться восстановить (false)
99         */
100        debouncedLoadTimeSlots: function(date, resetTime) {
101            var self = this;
102            
103            // По умолчанию resetTime = true (пользовательское изменение)
104            if (typeof resetTime === 'undefined') {
105                resetTime = true;
106            }
107
108            // Очищаем предыдущий таймер
109            if (this.debounceTimer) {
110                clearTimeout(this.debounceTimer);
111            }
112
113            // Устанавливаем новый таймер (300ms)
114            this.debounceTimer = setTimeout(function() {
115                self.loadTimeSlots(date, resetTime);
116            }, 300);
117        },
118
119        /**
120         * Загрузка интервалов времени для даты
121         */
122        loadTimeSlots: function(date, resetTime) {
123            var self = this;
124            var $timeField = $('#time, #billing_time');
125
126            // Показываем индикатор загрузки
127            $timeField.prop('disabled', true);
128            $timeField.html('<option value="">' + this.config.i18n.loading + '</option>');
129
130            // REST API запрос (дата уже в формате dmY, например "07112025")
131            $.ajax({
132                url: this.config.restUrl + '/calendar/time-slots',
133                method: 'GET',
134                data: {
135                    date: date
136                },
137                beforeSend: function(xhr) {
138                    xhr.setRequestHeader('X-WP-Nonce', self.config.nonce);
139                },
140                success: function(response) {
141                    if (response.success && response.data) {
142                        self.renderTimeSlots(response.data, resetTime);
143                    } else {
144                        self.showError();
145                    }
146                },
147                error: function(xhr, status, error) {
148                    console.error('CalendarField: Error loading time slots:', error, xhr.responseText);
149                    self.showError();
150                },
151                complete: function() {
152                    $timeField.prop('disabled', false);
153                }
154            });
155        },
156
157        /**
158         * Рендеринг интервалов времени
159         */
160        renderTimeSlots: function(timeSlots, resetTime) {
161            var $timeField = $('#time, #billing_time');
162            var savedTime = this.getFromStorage('Time');
163
164            // Очищаем поле
165            $timeField.empty();
166
167            // Проверяем, является ли timeSlots объектом или массивом
168            var isObject = typeof timeSlots === 'object' && !Array.isArray(timeSlots);
169            var isEmpty = isObject ? Object.keys(timeSlots).length === 0 : timeSlots.length === 0;
170
171            // Если слотов нет — показываем сообщение "Closed" и выходим
172            if (isEmpty) {
173                $timeField.append('<option value="">' + this.config.i18n.closed + '</option>');
174                // Триггерим change, чтобы очистить сохраненное значение, если оно было
175                $timeField.trigger('change');
176            } else {
177                var availableValues = [];
178
179                // Добавляем placeholder "Выберите время" с пустым значением
180                $timeField.append('<option value="">' + this.config.i18n.selectTime + '</option>');
181                
182                if (isObject) {
183                    // Обрабатываем объект {value: label}
184                    $.each(timeSlots, function(value, label) {
185                        $timeField.append('<option value="' + value + '">' + label + '</option>');
186                        availableValues.push(value);
187                    });
188                } else {
189                    // Обратная совместимость: массив строк
190                    $.each(timeSlots, function(index, time) {
191                        $timeField.append('<option value="' + time + '">' + time + '</option>');
192                        availableValues.push(time);
193                    });
194                }
195
196                // Выбираем время для установки
197                var timeToSelect = null;
198
199                if (resetTime !== true) {
200                    // При инициализации или восстановлении пытаемся восстановить сохраненное время
201                    if (this.restoreTimeFromStorage()) {
202                        // Время успешно восстановлено из localStorage, не триггерим change
203                        return;
204                    }
205                    // Если сохраненного времени нет или оно недоступно, оставляем пустое значение
206                }
207
208                // При смене даты (resetTime = true) или если нет сохраненного времени - устанавливаем пустое значение
209                $timeField.val('').trigger('change');
210            }
211        },
212
213        /**
214         * Показать ошибку загрузки
215         */
216        showError: function() {
217            var $timeField = $('#time, #billing_time');
218            $timeField.empty();
219            $timeField.append('<option value="">' + this.config.i18n.error + '</option>');
220        },
221
222        /**
223         * Сохранение в localStorage с TTL
224         */
225        saveToStorage: function(key, value) {
226            var storageKey = 'iikoCalendar' + key;
227            var ttl = this.config.ttlMinutes * 60 * 1000; // В миллисекундах
228            var now = new Date().getTime();
229
230            var data = {
231                value: value,
232                expires: now + ttl
233            };
234
235            try {
236                localStorage.setItem(storageKey, JSON.stringify(data));
237                // console.log('CalendarField: Saved to storage:', storageKey, value);
238            } catch (e) {
239                console.error('CalendarField: Error saving to storage:', e);
240            }
241        },
242
243        /**
244         * Получение из localStorage с проверкой TTL
245         */
246        getFromStorage: function(key) {
247            var storageKey = 'iikoCalendar' + key;
248            var now = new Date().getTime();
249
250            try {
251                var item = localStorage.getItem(storageKey);
252                
253                if (!item) {
254                    return null;
255                }
256
257                var data = JSON.parse(item);
258
259                // Проверка TTL
260                if (data.expires && data.expires < now) {
261                    localStorage.removeItem(storageKey);
262                    return null;
263                }
264
265                // console.log('CalendarField: Restored from storage:', storageKey, data.value);
266                return data.value;
267            } catch (e) {
268                console.error('CalendarField: Error reading from storage:', e);
269                return null;
270            }
271        },
272
273        /**
274         * Восстановление только даты из localStorage
275         * Время восстанавливается в renderTimeSlots после загрузки слотов
276         */
277        restoreDateFromStorage: function() {
278            var savedDate = this.getFromStorage('Date');
279
280            // Восстановление даты
281            if (savedDate) {
282                var $dateField = $('#date, #billing_date');
283                if ($dateField.length && $dateField.val() !== savedDate) {
284                    // Устанавливаем флаг программного восстановления
285                    this.isRestoring = true;
286                    
287                    $dateField.val(savedDate).trigger('change');
288                    
289                    // Сбрасываем флаг после небольшой задержки
290                    // (чтобы обработчик change успел выполниться)
291                    var self = this;
292                    setTimeout(function() {
293                        self.isRestoring = false;
294                    }, 50);
295                }
296            }
297        },
298
299        /**
300         * Восстановление времени из localStorage
301         * Вызывается после загрузки слотов времени в renderTimeSlots
302         */
303        restoreTimeFromStorage: function() {
304            var savedTime = this.getFromStorage('Time');
305
306            if (savedTime) {
307                var $timeField = $('#time, #billing_time');
308                // Проверяем, что опция с таким значением существует
309                if ($timeField.length && $timeField.find('option[value="' + savedTime + '"]').length) {
310                    // Устанавливаем значение БЕЗ trigger('change'), чтобы не вызвать повторное сохранение
311                    // и не создать бесконечный цикл при updated_checkout
312                    $timeField.val(savedTime);
313                    
314                    // Но триггерим событие для WooCommerce, чтобы значение попало в форму
315                    $timeField.trigger('input');
316                    
317                    return true; // Время успешно восстановлено
318                }
319            }
320            return false; // Время не восстановлено
321        },
322
323        /**
324         * Загрузка интервалов времени при первой загрузке страницы
325         * Вызывается один раз при инициализации
326         */
327        loadInitialTimeSlots: function() {
328            var $dateField = $('#date, #billing_date');
329            
330            // Проверяем, есть ли поле даты и имеет ли оно значение
331            if ($dateField.length && $dateField.val()) {
332                var date = $dateField.val();
333                
334                // Валидация: проверяем, что дата соответствует формату dmY
335                if (date.length === 8 && !isNaN(date)) {
336                    this.loadTimeSlots(date, false); // resetTime = false при инициализации
337                } else {
338                    console.warn('CalendarField: Invalid initial date format:', date);
339                }
340            }
341        }
342    };
343
344    /**
345     * Инициализация при загрузке DOM
346     */
347    $(document).ready(function() {
348        CalendarField.init();
349    });
350
351})(jQuery);

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.