PageSourceSearch

https://www.commune-nointel.fr/wp-content/plugins/hob-cityhall-booking/assets/js/frontend.js?ver=1.2.0

js commune-nointel.fr collected 2026-10-03 02:46:27 UTC 24,293 bytes, 620 lines download raw bytes

1(function($) {
2    'use strict';
3
4    // === GALLERY ===
5    $(document).on('click', '.hcb-gallery-thumb', function() {
6        var fullSrc = $(this).data('full');
7        $('#hcb-gallery-main-img').attr('src', fullSrc);
8        $('.hcb-gallery-thumb').removeClass('active');
9        $(this).addClass('active');
10    });
11    $('.hcb-gallery-thumb:first').addClass('active');
12
13    // === CALENDAR (Frontend) ===
14    function initFrontendCalendars() {
15        $('.hcb-calendar').each(function() {
16            var $cal = $(this);
17            var itemId = $cal.data('item-id');
18            var itemType = $cal.data('item-type');
19            var calendarEvents = []; // Stockage local des événements
20
21            $cal.fullCalendar({
22                locale: 'fr',
23                header: {
24                    left: 'prev,next today',
25                    center: 'title',
26                    right: 'month,agendaWeek'
27                },
28                defaultView: 'month',
29                editable: false,
30                eventLimit: true,
31                selectable: true,
32                selectHelper: true,
33                unselectAuto: true,
34                eventSources: [
35                    // Source 1 : réservations existantes
36                    {
37                        events: function(start, end, timezone, callback) {
38                            $.ajax({
39                                url: hcb_front.ajax_url,
40                                data: {
41                                    action: 'hcb_get_events',
42                                    item_id: itemId,
43                                    item_type: itemType,
44                                    start: start.format(),
45                                    end: end.format()
46                                },
47                                success: function(data) {
48                                    calendarEvents = data;
49                                    callback(data);
50                                }
51                            });
52                        }
53                    },
54                    // Source 2 : créneaux de disponibilité (fond vert)
55                    {
56                        events: function(start, end, timezone, callback) {
57                            $.ajax({
58                                url: hcb_front.ajax_url,
59                                data: {
60                                    action: 'hcb_get_availability_slots',
61                                    item_id: itemId,
62                                    item_type: itemType,
63                                    start: start.format(),
64                                    end: end.format()
65                                },
66                                success: function(data) {
67                                    if (data && data.success) {
68                                        callback(data.data.slots || []);
69                                    } else {
70                                        callback([]);
71                                    }
72                                },
73                                error: function() {
74                                    callback([]);
75                                }
76                            });
77                        }
78                    }
79                ],
80                eventRender: function(event, element) {
81                    if (event.rendering === 'background') {
82                        element.attr('title', hcb_front.strings.click_to_book || 'Cliquer pour réserver');
83                    } else {
84                        element.attr('title', event.title + ' (' + event.status_label + ')');
85                    }
86                },
87                select: function(start, end, jsEvent, view) {
88                    var dateStr, dateEnd;
89
90                    if (view.name === 'agendaWeek' || view.name === 'agendaDay') {
91                        // Vue semaine/jour : plage horaire sélectionnée
92                        dateStr = start.format('YYYY-MM-DD[T]HH:mm');
93                        // Si clic simple (30 min par défaut), étendre à 1 heure
94                        var diff = end.diff(start, 'minutes');
95                        if (diff <= 30) {
96                            dateEnd = start.clone().add(1, 'hour').format('YYYY-MM-DD[T]HH:mm');
97                        } else {
98                            dateEnd = end.format('YYYY-MM-DD[T]HH:mm');
99                        }
100                    } else {
101                        // Vue mois : heures par défaut
102                        dateStr = start.format('YYYY-MM-DD') + 'T09:00';
103                        dateEnd = start.format('YYYY-MM-DD') + 'T
10318:00';
104                    }
105
106                    // Vérifier si le créneau chevauche une réservation
107                    var clickStart = moment(dateStr);
108                    var clickEnd = moment(dateEnd);
109                    var conflict = false;
110
111                    for (var i = 0; i < calendarEvents.length; i++) {
112                        var ev = calendarEvents[i];
113                        if (ev.status === 'accepted' || ev.status === 'studying') {
114                            var evStart = moment(ev.start);
115                            var evEnd = moment(ev.end);
116                            if (clickStart.isBefore(evEnd) && clickEnd.isAfter(evStart)) {
117                                conflict = true;
118                                break;
119                            }
120                        }
121                    }
122
123                    if (conflict) {
124                        hcbShowToast(hcb_front.strings.slot_busy || 'Ce créneau est déjà réservé.');
125                        $cal.fullCalendar('unselect');
126                        return;
127                    }
128
129                    // Pré-remplir le formulaire
130                    $('#hcb-date-start').val(dateStr);
131                    $('#hcb-date-end').val(dateEnd);
132
133                    $('html, body').animate({
134                        scrollTop: $('#hcb-booking-form-section').offset().top - 80
135                    }, 500);
136                }
137            });
138        });
139    }
140
141    /**
142     * Affiche un message toast temporaire.
143     */
144    function hcbShowToast(message) {
145        var $toast = $('<div class="hcb-toast">' + message + '</div>');
146        $('body').append($toast);
147        setTimeout(function() { $toast.addClass('hcb-toast-visible'); }, 10);
148        setTimeout(function() {
149            $toast.removeClass('hcb-toast-visible');
150            setTimeout(function() { $toast.remove(); }, 300);
151        }, 3000);
152    }
153
154    // === BOOKING FORM ===
155    $(document).on('submit', '.hcb-booking-form', function(e) {
156        e.preventDefault();
157
158        var $form = $(this);
159        var $btn = $form.find('button[type=submit]');
160        var $msg = $form.find('.hcb-form-message');
161
162        // Validate
163        var required = ['first_name', 'last_name', 'email', 'date_start', 'date_end'];
164        var valid = true;
165        required.forEach(function(field) {
166            var val = $form.find('[name="' + field + '"]').val();
167            if (!val || val.trim() === '') {
168                valid = false;
169                $form.find('[name="' + field + '"]').css('border-color', '#d63638');
170            } else {
171                $form.find('[name="' + field + '"]').css('border-color', '');
172            }
173        });
174
175        // Check GDPR
176        if (!$form.find('[name="gdpr_consent"]').is(':checked')) {
177            valid = false;
178            $form.find('.hcb-form-gdpr').css('color', '#d63638');
179        } else {
180            $form.find('.hcb-form-gdpr').css('color', '');
181        }
182
183        if (!valid) {
184            $msg.removeClass('success').addClass('error').text(hcb_front.strings.fill_required).show();
185            return;
186        }
187
188        // Validation des fichiers joints
189        var allowedTypes = [
190            'application/pdf', 'image/jpeg', 'image/png',
191            'application/msword',
192            'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
193        ];
194        var maxSizeBytes = (hcb_front.max_file_size_mb || 5) * 1024 * 1024;
195        var fileValid = true;
196
197        $form.find('input[type="file"]').each(function() {
198            var input = this;
199            var label = $(input).data('label') || input.name;
200
201            if (input.required && (!input.files || input.files.length === 0)) {
202                fileValid = false;
203                $(input).css('border-color', '#d63638');
204                return true; // continue
205            }
206
207            if (input.files && input.files.length > 0) {
208                var file = input.files[0];
209                $(input).css('border-color', '');
210
211                if (file.size > maxSizeBytes) {
212                    fileValid = false;
213                    $msg.removeClass('success').addClass('error')
214                        .text((hcb_front.strings.file_too_large || '').replace('%s', label)).show();
215                    $(input).css('border-color', '#d63638');
216                    return false; // break
217                }
218
219                if (allowedTypes.indexOf(file.type) === -1 && file.type !== '') {
220                    fileValid = false;
221                    $msg.removeClass('success').addClass('error')
222                        .text((hcb_front.strings.file_type_invalid || '').replace('%s', label)).show();
223                    $(input).css('border-color', '#d63638');
224                    return false; // break
225                }
226            }
227        });
228
229        if (!fileValid) {
230            if (!$msg.is(':visible')) {
231                $msg.removeClass('success').addClass('error').text(hcb_front.strings.docs_required || '').show();
232            }
233            return;
234        }
235
236        // Submit avec FormData (supporte les fichiers)
237        $btn.addClass('hcb-btn-loading').prop('disabled', true);
238        $msg.hide();
239
240        var fd = new FormData($form[0]);
241        fd.append('action', 'hcb_submit_booking');
242        fd.append('nonce', hcb_front.nonce);
243        fd.append('item_id', $form.data('item-id'));
244        fd.append('item_type', $form.data('item-type'));
245
246        $.ajax({
247            url: hcb_front.ajax_url,
248            method: 'POST',
249            data: fd,
250            processData: false,
251            contentType: false,
252            success: function(response) {
253                $btn.removeClass('hcb-btn-loading').prop('disabled', false);
254                if (response.success) {
255                    $msg.removeClass('error').addClass('success').text(response.data.message).show();
256                    $form[0].reset();
257                    $('.hcb-calendar').each(function() {
258                        $(this).fullCalendar('refetchEvents');
259                    });
260                } else {
261                    $msg.removeClass('success').addClass('error').text(response.data.message || hcb_front.strings.error).show();
262                }
263            },
264            error: function() {
265                $btn.removeClass('hcb-btn-loading').prop('disabled', false);
266                $msg.removeClass('success').addClass('error').text(hcb_front.strings.error).show();
267            }
268        });
269    });
270
271    // === FILTRES AVANCÉS ===
272    var filterTimeout;
273
274    function applyRoomFilters() {
275        var $bar = $('#hcb-room-filters');
276        var $results = $('#hcb-rooms-results');
277        if (!$bar.length || !$results.length) return;
278
279        var filters = {
280            action: 'hcb_filter_rooms',
281            nonce: hcb_front.nonce,
282            capacity_min: $bar.find('[name="capacity_min"]').val(),
283            capacity_max: $bar.find('[name="capacity_max"]').val(),
284            amenities: [],
285            accessibility: $bar.find('[name="accessibility"]').val(),
286            parking: $bar.find('[name="parking"]').val(),
287            price_min: $bar.find('[name="price_min"]').val(),
288            price_max: $bar.find('[name="price_max"]').val(),
289            available_date: $bar.find('[name="available_date"]').val(),
290            category: $bar.find('[name="category"]').val(),
291            free_for_assoc: $bar.find('[name="free_for_assoc"]').is(':checked') ? 1 : 0
292        };
293
294        $bar.find('[name="amenities[]"]:checked').each(function() {
295            filters.amenities.push($(this).val());
296        });
297
298        $results.addClass('hcb-loading');
299
300        $.ajax({
301            url: hcb_front.ajax_url,
302            method: 'POST',
303            data: filters,
304            success: function(response) {
305                $results.removeClass('hcb-loading');
306                if (response.success) {
307                    $results.html(response.data.html);
308                    $bar.find('.hcb-filter-count').text(response.data.count + ' résultat(s)');
309                    // Restaurer l'état des checkboxes de comparaison
310                    restoreCompareCheckboxes();
311                }
312            },
313            error: function() {
314                $results.removeClass('hcb-loading');
315            }
316        });
317    }
318
319    function applyEquipmentFilters() {
320        var $bar = $('#hcb-equipment-filters');
321        var $results = $('#hcb-equipment-results');
322        if (!$bar.length || !$results.length) return;
323
324        var filters = {
325            action: 'hcb_filter_equipment',
326            nonce: hcb_front.nonce,
327            category: $bar.find('[name="category"]').val(),
328            price_min: $bar.find('[name="price_min"]').val(),
329            price_max: $bar.find('[name="price_max"]').val(),
330            available_date: $bar.find('[name="available_date"]').val()
331        };
332
333        $results.addClass('hcb-loading');
334
335        $.ajax({
336            url: hcb_front.ajax_url,
337            method: 'POST',
338            data: filters,
339            success: function(response) {
340                $results.removeClass('hcb-loading');
341                if (response.success) {
342                    $results.html(response.data.html);
343                    $bar.find('.hcb-filter-count').text(response.data.count + ' résultat(s)');
344                }
345            },
346            error: function() {
347                $results.removeClass('hcb-loading');
348            }
349        });
350    }
351
352    // Filtres salles : debounce
353    $(document).on('change input', '#hcb-room-filters input, #hcb-room-filters select', function() {
354        clearTimeout(filterTimeout);
355        filterTimeout = setTimeout(applyRoomFilters, 400);
356    });
357
358    // Filtres équipements : debounce
359    $(document).on('change input', '#hcb-equipment-filters input, #hcb-equipment-filters select', function() {
360        clearTimeout(filterTimeout);
361        filterTimeout = setTimeout(applyEquipmentFilters, 400);
362    });
363
364    // Réinitialiser les filtres
365    $(document).on('click', '#hcb-room-filters .hcb-filter-reset', function() {
366        var $bar = $('#hcb-room-filters');
367        $bar.find('input[type="number"], input[type="date"]').val('');
368        $bar.find('select').val('');
369        $bar.find('input[type="checkbox"]').prop('checked', false);
370        $bar.find('.hcb-filter-count').text('');
371        applyRoomFilters();
372    });
373
374    $(document).on('click', '#hcb-equipment-filters .hcb-filter-reset', function() {
375        var $bar = $('#hcb-equipment-filters');
376        $bar.find('input[type="number"], input[type="date"]').val('');
377        $bar.find('select').val('');
378        $bar.find('.hcb-filter-count').text('');
379        applyEquipmentFilters();
380    });
381
382    // Dérouler/replier les équipements
383    $(document).on('click', '.hcb-filter-amenities-toggle', function() {
384        $(this).closest('.hcb-filter-amenities-section').find('.hcb-filter-amenities').slideToggle(200);
385    });
386
387    // === COMPARAISON DE SALLES ===
388    var MAX_COMPARE = 3;
389
390    function getSelectedRooms() {
391        try {
392            return JSON.parse(sessionStorage.getItem('hcb_compare') || '[]');
393        } catch (e) {
394            return [];
395        }
396    }
397
398    function saveSelectedRooms(rooms) {
399        sessionStorage.setItem('hcb_compare', JSON.stringify(rooms));
400    }
401
402    function restoreCompareCheckboxes() {
403        var selected = getSelectedRooms();
404        $('.hcb-compare-check').each(function() {
405            var id = parseInt($(this).val());
406            $(this).prop('checked', selected.indexOf(id) !== -1);
407        });
408        updateCompareBar();
409    }
410
411    function updateCompareBar() {
412        var selected = getSelectedRooms();
413        var $bar = $('#hcb-compare-bar');
414        if (!$bar.length) return;
415
416        if (selected.length === 0) {
417            $bar.slideUp(200);
418            return;
419        }
420
421        $bar.slideDown(200);
422        $bar.find('.hcb-compare-count').text(selected.length + '/' + MAX_COMPARE);
423        $bar.find('.hcb-compare-btn').prop('disabled', selected.length < 2);
424
425        // Construire la liste des miniatures
426        var $items = $bar.find('.hcb-compare-items');
427        $items.empty();
428        selected.forEach(function(id) {
429            var $card = $('[data-room-id="' + id + '"]');
430            var title = $card.find('.hcb-item-title a').text() || '#' + id;
431            var thumb = $card.find('.hcb-item-image img').attr('src') || '';
432            var html = '<div class="hcb-compare-item-mini">';
433            if (thumb) {
434                html += '<img src="' + thumb + '" alt="">';
435            }
436            html += '<span>' + title + '</span>';
437            html += '<button type="button" class="hcb-compare-remove" data-id="' + id + '">&times;</button>';
438            html += '</div>';
439            $items.append(html);
440        });
441
442        // Désactiver les checkboxes restantes si max atteint
443        if (selected.length >= MAX_COMPARE) {
444            $('.hcb-compare-check').each(function() {
445                if (!$(this).is(':checked')) {
446                    $(this).prop('disabled', true);
447                }
448            });
449        } else {
450            $('.hcb-compare-check').prop('disabled', false);
451        }
452    }
453
454    // Clic sur checkbox de comparaison
455    $(document).on('change', '.hcb-compare-check', function() {
456        var id = parseInt($(this).val());
457        var selected = getSelectedRooms();
458
459        if ($(this).is(':checked')) {
460            if (selected.length < MAX_COMPARE && selected.indexOf(id) === -1) {
461                selected.push(id);
462            }
463        } else {
464            selected = selected.filter(function(v) { return v !== id; });
465        }
466
467        saveSelectedRooms(selected);
468        updateCompareBar();
469    });
470
471    // Supprimer de la barre de comparaison
472    $(document).on('click', '.hcb-compare-remove', function() {
473        var id = parseInt($(this).data('id'));
474        var selected = getSelectedRooms().filter(function(v) { return v !== id; });
475        saveSelectedRooms(selected);
476        $('.hcb-compare-check[value="' + id + '"]').prop('checked', false);
477        updateCompareBar();
478    });
479
480    // Vider la comparaison
481    $(document).on('click', '.hcb-compare-clear', function() {
482        saveSelectedRooms([]);
483        $('.hcb-compare-check').prop('checked', false);
484        updateCompareBar();
485    });
486
487    // Ouvrir la modale de comparaison
488    $(document).on('click', '.hcb-compare-btn', function() {
489        var selected = getSelectedRooms();
490        if (selected.length < 2) return;
491
492        var $modal = $('#hcb-compare-modal');
493        var $wrap = $modal.find('.hcb-compare-table-wrap');
494        $wrap.html('<p style="text-align:center;">' + (hcb_front.strings.loading || 'Chargement...') + '</p>');
495        $modal.fadeIn(200);
496
497        $.ajax({
498            url: hcb_front.ajax_url,
499            method: 'POST',
500            data: {
501                action: 'hcb_get_comparison_data',
502                nonce: hcb_front.nonce,
503                room_ids: selected
504            },
505            success: function(response) {
506                if (response.success) {
507                    $wrap.html(buildComparisonTable(response.data.rooms, response.data.amenity_labels));
508                } else {
509                    $wrap.html('<p class="hcb-no-results">' + (response.data.message || hcb_front.strings.error) + '</p>');
510                }
511            },
512            error: function() {
513                $wrap.html('<p class="hcb-no-results">' + hcb_front.strings.error + '</p>');
514            }
515        });
516    });
517
518    // Construire le tableau de comparaison
519    function buildComparisonTable(rooms, amenityLabels) {
520        var html = '<table class="hcb-compare-table"><thead><tr><th></th>';
521        rooms.forEach(function(r) { html += '<th>' + escHtml(r.title) + '</th>'; });
522        html += '</tr></thead><tbody>';
523
524        // Photo
525        html += '<tr><td><strong>Photo</strong></td>';
526        rooms.forEach(function(r) {
527            html += '<td>';
528            if (r.thumbnail) html += '<img src="' + r.thumbnail + '" class="hcb-compare-thumb" alt="">';
529            else html += '-';
530            html += '</td>';
531        });
532        html += '</tr>';
533
534        // Lignes de données
535        var rows = [
536            ['Capacité assise', function(r) { return r.capacity ? r.capacity + ' pers.' : '-'; }],
537            ['Capacité debout', function(r) { return r.capacity_standing ? r.capacity_standing + ' pers.' : '-'; }],
538            ['Superficie', function(r) { return r.surface ? r.surface + ' m²' : '-'; }],
539            ['Prix / heure', function(r) { return r.price_hour ? formatPrice(r.price_hour) + ' €' : '-'; }],
540            ['Prix / jour', function(r) { return r.price_day ? formatPrice(r.price_day) + ' €' : '-'; }],
541            ['Caution', function(r) { return r.deposit ? formatPrice(r.deposit) + ' €' : '-'; }],
542            ['Accessibilité PMR', function(r) { return r.accessibility || '-'; }],
543            ['Parking', function(r) { return r.parking || '-'; }],
544            ['Gratuit assos', function(r) { return r.free_for_assoc ? '✓' : '✗'; }]
545        ];
546
547        rows.forEach(function(row) {
548            html += '<tr><td><strong>' + row[0] + '</strong></td>';
549            rooms.forEach(function(r) { html += '<td>' + row[1](r) + '</td>'; });
550            html += '</tr>';
551        });
552
553        // Équipements
554        if (amenityLabels) {
555            for (var key in amenityLabels) {
556                html += '<tr><td><strong>' + escHtml(amenityLabels[key]) + '</strong></td>';
557                rooms.forEach(function(r) {
558                    var has = r.amenities && r.amenities[key];
559                    html += '<td class="' + (has ? 'hcb-amenity-yes' : 'hcb-amenity-no') + '">' + (has ? '✓' : '✗') + '</td>';
560                });
561                html += '</tr>';
562            }
563        }
564
565        // Boutons
566        html += '<tr><td></td>';
567        rooms.forEach(function(r) {
568            html += '<td><a href="' + r.permalink + '" class="hcb-btn hcb-btn-primary">Voir &amp; Réserver</a></td>';
569        });
570        html += '</tr>';
571
572        html += '</tbody></table>';
573        return html;
574    }
575
576    function formatPrice(val) {
577        return parseFloat(val).toFixed(2).replace('.', ',');
578    }
579
580    function escHtml(str) {
581        var div = document.createElement('div');
582        div.appendChild(document.createTextNode(str));
583        return div.innerHTML;
584    }
585
586    // Fermer la modale
587    $(document).on('click', '.hcb-compare-modal-close', function() {
588        $('#hcb-compare-modal').fadeOut(200);
589    });
590    $(document).on('click', '#hcb-compare-modal', function(e) {
591        if ($(e.target).is('#hcb-compare-modal')) {
592            $(this).fadeOut(200);
593        }
594    });
595    $(document).on('keydown', function(e) {
596        if (e.key === 'Escape') {
597            $('#hcb-compare-modal').fadeOut(200);
598        }
599    });
600
601    // === PRÉ-REMPLISSAGE FORMULAIRE UTILISATEUR CONNECTÉ ===
602    function prefillUserData() {
603        if (typeof hcb_front.user !== 'undefined' && hcb_front.user.email) {
604            var $form = $('.hcb-booking-form');
605            if ($form.length) {
606                $form.find('[name="first_name"]').val(hcb_front.user.first_name);
607                $form.find('[name="last_name"]').val(hcb_front.user.last_name);
608                $form.find('[name="email"]').val(hcb_front.user.email).prop('readonly', true);
609            }
610        }
611    }
612
613    // === INIT ===
614    $(document).ready(function() {
615        initFrontendCalendars();
616        restoreCompareCheckboxes();
617        prefillUserData();
618    });
619
620})(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.