1/* ----------------- Start Document ----------------- */ 2(function($){ 3"use strict"; 4 5$(document).ready(function(){ 6 var inputClicked = false; 7 /*----------------------------------------------------*/ 8 /* Booking widget and confirmation form 9 /*----------------------------------------------------*/ 10 $("a.booking-confirmation-btn").on("click", function (e) { 11 e.preventDefault(); 12 var bookingForm = $("#booking-confirmation"); 13 if (bookingForm[0].checkValidity()) { 14 var button = $(this); 15 button.addClass("loading"); 16 $("#booking-confirmation").submit(); 17 } 18 }); 19 20 // reload states based on country 21 function loadStatesForCountry(country) { 22 if (!country) { 23 return; 24 } 25 26 $.ajax({ 27 url: listeo.ajaxurl, // WordPress AJAX 28 type: "POST", 29 data: { 30 action: "listeo_get_booking_states", 31 country: country, 32 nonce: listeoBookings.states_nonce, 33 }, 34 success: function (response) { 35 if (response.success && response.data) { 36 var states = response.data; 37 var stateField = $('[name="billing_state"]'); 38 39 if (Object.keys(states).length > 0) { 40 // Country has states - create/replace with select dropdown 41 var selectHtml = '<select name="billing_state" id="billing_state" class="address-field" required>'; 42 selectHtml += '<option value="">' + listeoBookings.select_state_text + '</option>'; 43 44 $.each(states, function (code, name) { 45 selectHtml += '<option value="' + code + '">' + name + '</option>'; 46 }); 47 selectHtml += '</select>'; 48 49 stateField.replaceWith(selectHtml); 50 } else { 51 // No states available - create/replace with text input 52 var inputHtml = '<input type="text" name="billing_state" id="billing_state" class="input-text" value="" placeholder="' + listeoBookings.state_placeholder + '">'; 53 stateField.replaceWith(inputHtml); 54 } 55 } 56 }, 57 }); 58 } 59 60 // Handle country change event (when dropdown is visible) 61 $("#billing_country").change(function () { 62 loadStatesForCountry($(this).val()); 63 }); 64 65 // Handle page load - check if country is pre-selected or hidden 66 $(document).ready(function () { 67 var countryField = $("#billing_country"); 68 if (countryField.length > 0) { 69 var country = countryField.val(); 70 71 // If country has a value (either hidden field or pre-selected dropdown) 72 // and state field is currently a text input, load states 73 if (country && $('[name="billing_state"]').is('input[type="text"]')) { 74 loadStatesForCountry(country); 75 } 76 } 77 }); 78 $(document).bind("change", function (e) { 79 if (e.target.checkValidity && !e.target.checkValidity()) { 80 $(e.target).parent().addClass("invalid"); 81 } else { 82 $(e.target).parent().removeClass("invalid"); 83 } 84 }); 85 86 $("#listeo-coupon-link").on("click", function (e) { 87 e.preventDefault(); 88 $(".coupon-form").toggle(); 89 }); 90 91 function validate_coupon(listing_id, price) { 92 var current_codes = $("#coupon_code").val(); 93 if (current_codes) { 94 var codes = current_codes.split(","); 95 $.each(codes, function (index, item) { 96 console.log(item); 97 var ajax_data = { 98 listing_id: listing_id, 99 coupon: item, 100 coupons: codes, 101 price: price, 102 action: "listeo_validate_coupon", 103 }; 104 $.ajax({ 105 type: "POST", 106 dataType: "json", 107 url: listeo.ajaxurl, 108 data: ajax_data, 109 110 success: function (data) { 111 if (data.success) { 112 } else { 113 $("#coupon-widget-wrapper-output div.error") 114 .html(data.message) 115 .show(); 116 $( 117 '#coupon-widget-wrapper-applied-coupons span[data-coupon="' + 118 item + 119 '"] i' 120 ).trigger("click"); 121 $("#apply_new_coupon").val(""); 122 $("#coupon-widget-wrapper-output .error").delay(3500).hide(500); 123 } 124 $("a.listeo-booking-widget-apply_new_coupon").removeClass("active"); 125 }, 126 }); 127 }); 128 } 129 } 130 131 // Apply new coupon 132 $("a.listeo-booking-widget-apply_new_coupon").on("click", function (e) { 133 e.preventDefault(); 134 $(this).addClass("active"); 135 $("#coupon-widget-wrapper-output div").hide(); 136 137 138 139 //check if it was already addd 140 var price = $(".booking-estimated-cost").data("price"); 141 if (typeof price === "undefined" || price === null) { 142 price = $(".total-costs").data("price"); 143 } 144 145 var current_codes = $("#coupon_code").val(); 146 var result = current_codes.split(","); 147 var arraycontainscoupon = result.indexOf($("#apply_new_coupon").val()) > -1; 148 var ajax_data = { 149 listing_id: $("#listing_id").val(), 150 coupon: $("#apply_new_coupon").val(), 151 coupons: current_codes, 152 price: price, 153 action: "listeo_validate_coupon", 154 155 }; 156 $("#coupon-widget-wrapper-output div").hide(); 157 if (arraycontainscoupon) { 158 $(this).removeClass("active"); 159 $("input#apply_new_coupon").removeClass("bounce").addClass("bounce"); 160 return; 161 } 162 $.ajax({ 163 type: "POST", 164 dataType: "json",
165 url: listeo.ajaxurl, 166 data: ajax_data, 167 168 success: function (data) { 169 if (data.success) { 170 if (current_codes.length > 0) { 171 $("#coupon_code").val(current_codes + "," + data.coupon); 172 } else { 173 $("#coupon_code").val(data.coupon); 174 } 175 $("#apply_new_coupon").val(""); 176 $("#coupon-widget-wrapper-applied-coupons").append( 177 "<span data-coupon=" + 178 data.coupon + 179 ">" + 180 data.coupon + 181 "<i class='fa fa-times'></i></span>" 182 ); 183 $("#coupon-widget-wrapper-output .success").show(); 184 if ($("#booking-confirmation-summary").length > 0) { 185 calculate_booking_form_price(); 186 } else { 187 if ($("#form-booking").hasClass("form-booking-tickets")) { 188 calculate_price(); 189 } else { 190 check_booking(); 191 } 192 } 193 $("#coupon-widget-wrapper-output .success").delay(3500).hide(500); 194 } else { 195 $("input#apply_new_coupon").removeClass("bounce").addClass("bounce"); 196 $("#coupon-widget-wrapper-output div.error") 197 .html(data.message) 198 .show(); 199 200 $("#apply_new_coupon").val(""); 201 $("#coupon-widget-wrapper-output .error").delay(3500).hide(500); 202 } 203 $("a.listeo-booking-widget-apply_new_coupon").removeClass("active"); 204 }, 205 }); 206 }); 207 208 // Remove coupon from widget and calculate price again 209 $("#coupon-widget-wrapper-applied-coupons").on( 210 "click", 211 "span i", 212 function (e) { 213 var coupon = $(this).parent().data("coupon"); 214 215 var coupons = $("#coupon_code").val(); 216 var coupons_array = coupons.split(","); 217 coupons_array = coupons_array.filter(function (item) { 218 return item !== coupon; 219 }); 220 221 $("#coupon_code").val(coupons_array.join(",")); 222 $(this).parent().remove(); 223 if ($("#booking-confirmation-summary").length > 0) { 224 calculate_booking_form_price(); 225 } else { 226 check_booking(); 227 calculate_price(); 228 } 229 } 230 ); 231 232 //Book now button 233 $(".listing-widget").on("click", "a.book-now", function (e) { 234 var button = $(this); 235 236 if ($("#date-picker").val()) { 237 inputClicked = true; 238 239 if ($(".time-picker").length && !$(".time-picker").val()) { 240 inputClicked = false; 241 } 242 if ($("#slot").length && !$("#slot").val()) { 243 inputClicked = false; 244 } 245 if (inputClicked) { 246 check_booking(); 247 } 248 } 249 250 if (inputClicked == false) { 251 $( 252 ".time-picker,.time-slots-dropdown,.date-picker-listing-date_range" 253 ).addClass("bounce"); 254 } else { 255 button.addClass("loading"); 256 } 257 e.preventDefault(); 258 259 var freeplaces = button.data("freeplaces"); 260 261 // var checked = $(".bookable-services input[type=checkbox]:checked").length; 262 263 // if (!checked) { 264 // alert("You must select at least one extra service."); 265 // return false; 266 // } 267 268 setTimeout(function () { 269 button.removeClass("loading"); 270 $( 271 ".time-picker,.time-slots-dropdown,.date-picker-listing-date_range" 272 ).removeClass("bounce"); 273 }, 3000); 274 275 try { 276 // Calculate total guests 277 var adults = parseInt($(".adults").val()) || 0; 278 var children = parseInt($("input[name=childrenQtyInput]").val()) || 0; 279 var infants = parseInt($("input[name=infantsQtyInput]").val()) || 0; 280 var animals = parseInt($("input[name=animalsQtyInput]").val()) || 0; 281 282 // Get max guests from the form 283 var maxGuests = parseInt($(".adults").data("max")); 284 285 // Calculate total counting guests (adults + children count towards max, infants and animals don't) 286 var totalCountingGuests = adults + children; 287 288 if (totalCountingGuests > maxGuests) { 289 $("#booking-widget-message").show().html(listeo_core.exceed_guests_limit); 290 return false; 291 } 292 293 if (freeplaces > 0) { 294 // preparing data for ajax to send to booking form 295 296 if ($("#date-picker").data("rental-timepicker")) { 297 // var startDataSQL need to have hour 298 var timeFormat = is24Hour ? "HH:mm" : "hh:mm A"; 299 var startDataSql = moment( 300 $("#date-picker").data("daterangepicker").startDate, 301 ["MM/DD/YYYY " + timeFormat] 302 ).format("YYYY-MM-DD HH:mm:ss"); 303 var endDataSql = moment( 304 $("#date-picker").data("daterangepicker").endDate, 305 ["MM/DD/YYYY " + timeFormat] 306 ).format("YYYY-MM-DD HH:mm:ss"); 307 } else { 308 var startDataSql = moment( 309 $("#date-picker").data("daterangepicker").startDate, 310 ["MM/DD/YYYY"] 311 ).format("YYYY-MM-DD"); 312 var endDataSql = moment( 313 $("#date-picker").data("daterangepicker").endDate, 314 ["MM/DD/YYYY"] 315 ).format("YYYY-MM-DD"); 316 } 317 318 var ajax_data = { 319 listing_type: $("#listing_type").val(), 320 listing_id: $("#listing_id").val(), 321 //'nonce': nonce 322 }; 323 var invalid = false;
324 if (startDataSql) ajax_data.date_start = startDataSql; 325 if (endDataSql) ajax_data.date_end = endDataSql; 326 if ($("input#slot").val()) ajax_data.slot = $("input#slot").val(); 327 if ($(".time-picker#_hour").val()) 328 ajax_data._hour = $(".time-picker#_hour").val(); 329 if ($(".time-picker#_hour_end").val()) 330 ajax_data._hour_end = $(".time-picker#_hour_end").val(); 331 if ($(".adults").val()) ajax_data.adults = $(".adults").val(); 332 if ($("input[name=childrenQtyInput]").val()) ajax_data.children = $("input[name=childrenQtyInput]").val(); 333 if ($("input[name=infantsQtyInput]").val()) ajax_data.infants = $("input[name=infantsQtyInput]").val(); 334 if ($("input[name=animalsQtyInput]").val()) ajax_data.animals = $("input[name=animalsQtyInput]").val(); 335 if ($("#tickets").val()) ajax_data.tickets = $("#tickets").val(); 336 if ($("#coupon_code").val()) ajax_data.coupon = $("#coupon_code").val(); 337 338 // Check if listing type supports time slots 339 var listing_type = $("#listing_type").val(); 340 var type_config = listeo_core.listing_types_config && listeo_core.listing_types_config[listing_type]; 341 var supports_time_slots = type_config ? type_config.supports_time_slots : (listing_type == "single_day"); // fallback 342 343 if (supports_time_slots) { 344 if ( 345 $("input#slot").val() == undefined || 346 $("input#slot").val() == "" 347 ) { 348 inputClicked = false; 349 invalid = true; 350 } 351 if ($(".time-picker").length) { 352 invalid = false; 353 } 354 } 355 356 if (invalid == false) { 357 var services = []; 358 // $.each($("input[name='_service[]']:checked"), function(){ 359 // services.push($(this).val()); 360 // }); 361 $.each($("input.bookable-service-checkbox:checked"), function () { 362 var quantity = $(this) 363 .parent() 364 .find("input.bookable-service-quantity") 365 .val(); 366 services.push({ service: $(this).val(), value: quantity }); 367 }); 368 ajax_data.services = services; 369 $("input#booking").val(JSON.stringify(ajax_data)); 370 371 $("#form-booking").submit(); 372 } 373 } 374 } catch (e) { 375 console.log(e); 376 } 377 378 // Check if listing type is event-based (no time slots support) 379 var listing_type = $("#listing_type").val(); 380 var type_config = listeo_core.listing_types_config && listeo_core.listing_types_config[listing_type]; 381 var is_event_type = type_config ? !type_config.supports_time_slots : (listing_type == "event"); // fallback 382 383 if (is_event_type) { 384 var ajax_data = { 385 listing_type: $("#listing_type").val(), 386 listing_id: $("#listing_id").val(), 387 date_start: $(".booking-event-date span").html(), 388 date_end: $(".booking-event-date span").html(), 389 //'nonce': nonce 390 }; 391 if ($("#coupon_code").val()) ajax_data.coupon = $("#coupon_code").val(); 392 var services = []; 393 $.each($("input.bookable-service-checkbox:checked"), function () { 394 var quantity = $(this) 395 .parent() 396 .find("input.bookable-service-quantity") 397 .val(); 398 services.push({ service: $(this).val(), value: quantity }); 399 }); 400 ajax_data.services = services; 401 402 // converent data 403 ajax_data["date_start"] = moment( 404 ajax_data["date_start"], 405 wordpress_date_format.date 406 ).format("YYYY-MM-DD"); 407 ajax_data["date_end"] = moment( 408 ajax_data["date_end"], 409 wordpress_date_format.date 410 ).format("YYYY-MM-DD"); 411 if ($("#tickets").val()) ajax_data.tickets = $("#tickets").val(); 412 $("input#booking").val(JSON.stringify(ajax_data)); 413 414 $("#form-booking").submit(); 415 } 416 }); 417 418 if (Boolean(listeo_core.clockformat)) { 419 var dateformat_even = wordpress_date_format.date + " HH:mm"; 420 } else { 421 var dateformat_even = wordpress_date_format.date + " hh:mm A"; 422 } 423 424 function updateCounter() { 425 var len = $(".bookable-services input[type='checkbox']:checked").length; 426 if (len > 0) { 427 $(".booking-services span.services-counter").text("" + len + ""); 428 $(".booking-services span.services-counter").addClass("counter-visible"); 429 } else { 430 $(".booking-services span.services-counter").removeClass( 431 "counter-visible" 432 ); 433 $(".booking-services span.services-counter").text("0"); 434 } 435 } 436 437 $(".single-service").on("click", function () { 438 updateCounter(); 439 $(".booking-services span.services-counter").addClass("rotate-x"); 440 441 setTimeout(function () { 442 $(".booking-services span.services-counter").removeClass("rotate-x"); 443 }, 300); 444 }); 445 446 // $( ".input-datetime" ).each(function( index ) { 447 // var $this = $(this); 448 // var input = $(this).next('input'); 449 // var date = parseInt(input.val()); 450 // if(date){ 451 // var a = new Date(date); 452 // var timestamp = moment(a); 453 // $this.val(timestamp.format(dateformat_even)); 454 // } 455 456 // }); 457 458 //$('#_event_date').val(timestamp.format(dateformat_even)); 459 460 // there are two fields with id _event_date and _event_date_end, make sure the event_date_end is not before event_date 461 // $('#_event_date_end').on('change', function() { 462 // alert('test'); 463 // var start_date = $('#_event_date').val(); 464 // var end_date = $('#_event_date_end').val(); 465 // if(end_date < start_date){ 466 // $('#_event_date_end').val(start_date); 467 // } 468 // }); 469window.initializeDateRangePicker = function ($context = $(document)) {
470 $context.find(".input-datetime").each(function () { 471 // Prevent double init 472 if (!$(this).data("daterangepicker")) { 473 $(this).daterangepicker({ 474 opens: "left", 475 singleDatePicker: true, 476 timePicker: true, 477 autoUpdateInput: false, 478 timePicker24Hour: Boolean(listeo_core.clockformat), 479 minDate: moment().subtract(0, "days"), 480 locale: { 481 format: dateformat_even, 482 firstDay: parseInt(wordpress_date_format.day), 483 applyLabel: listeo_core.applyLabel, 484 cancelLabel: listeo_core.cancelLabel, 485 fromLabel: listeo_core.fromLabel, 486 toLabel: listeo_core.toLabel, 487 customRangeLabel: listeo_core.customRangeLabel, 488 daysOfWeek: [ 489 listeo_core.day_short_su, 490 listeo_core.day_short_mo, 491 listeo_core.day_short_tu, 492 listeo_core.day_short_we, 493 listeo_core.day_short_th, 494 listeo_core.day_short_fr, 495 listeo_core.day_short_sa, 496 ], 497 monthNames: [ 498 listeo_core.january, 499 listeo_core.february, 500 listeo_core.march, 501 listeo_core.april, 502 listeo_core.may, 503 listeo_core.june, 504 listeo_core.july, 505 listeo_core.august, 506 listeo_core.september, 507 listeo_core.october, 508 listeo_core.november, 509 listeo_core.december, 510 ], 511 }, 512 }); 513 } 514 }); 515}; 516initializeDateRangePicker(); 517 518 $(".input-datetime").on("apply.daterangepicker", function (ev, picker) { 519 $(this).val(picker.startDate.format(dateformat_even)); 520 var end_date = $("#_event_date_end").length; 521 if (end_date) { 522 $(".input-datetime#_event_date_end") 523 .data("daterangepicker") 524 .setMinDate(picker.startDate.format(dateformat_even)); 525 } 526 }); 527 528 $(".input-datetime").on("cancel.daterangepicker", function (ev, picker) { 529 $(this).val(""); 530 }); 531 532 $(".input-date").daterangepicker({ 533 opens: "left", 534 // checking attribute listing type and set type of calendar 535 singleDatePicker: true, 536 timePicker: false, 537 autoUpdateInput: false, 538 539 minDate: moment().subtract(0, "days"), 540 541 locale: { 542 format: dateformat_even, 543 firstDay: parseInt(wordpress_date_format.day), 544 applyLabel: listeo_core.applyLabel, 545 cancelLabel: listeo_core.cancelLabel, 546 fromLabel: listeo_core.fromLabel, 547 toLabel: listeo_core.toLabel, 548 customRangeLabel: listeo_core.customRangeLabel, 549 daysOfWeek: [ 550 listeo_core.day_short_su, 551 listeo_core.day_short_mo, 552 listeo_core.day_short_tu, 553 listeo_core.day_short_we, 554 listeo_core.day_short_th, 555 listeo_core.day_short_fr, 556 listeo_core.day_short_sa, 557 ], 558 monthNames: [ 559 listeo_core.january, 560 listeo_core.february, 561 listeo_core.march, 562 listeo_core.april, 563 listeo_core.may, 564 listeo_core.june, 565 listeo_core.july, 566 listeo_core.august, 567 listeo_core.september, 568 listeo_core.october, 569 listeo_core.november, 570 listeo_core.december, 571 ], 572 }, 573 }); 574 575 $(".input-date").on("apply.daterangepicker", function (ev, picker) { 576 $(this).val(picker.startDate.format("YYYY-MM-DD")); 577 }); 578 579 $(".input-date").on("cancel.daterangepicker", function (ev, picker) { 580 $(this).val(""); 581 }); 582 // $('.input-datetime').on( 'apply.daterangepicker', function(){ 583 584 // var picked_date = $(this).val(); 585 // var input = $(this).next('input'); 586 // input.val(moment(picked_date,dateformat_even).format('YYYY-MM-DD HH:MM:SS')); 587 // } ); 588 589 function wpkGetThisDateSlots(date) { 590 var slots = { 591 isFirstSlotTaken: false, 592 isSecondSlotTaken: false, 593 }; 594 595 // Check if listing type supports time slots 596 var listing_type = $("#listing_type").val(); 597 var type_config = listeo_core.listing_types_config && listeo_core.listing_types_config[listing_type]; 598 var supports_time_slots = type_config ? type_config.supports_time_slots : (listing_type != "event"); // fallback 599 600 if (!supports_time_slots) return slots; 601 602 var dateString = date.format("YYYY-MM-DD"); 603 604 // Check disabled dates first 605 if (typeof disabledDates !== "undefined") { 606 if (wpkIsDateInArray(date, disabledDates)) { 607 slots.isFirstSlotTaken = slots.isSecondSlotTaken = true; 608 console.log("Date " + dateString + " is in disabled dates"); 609 return slots; 610 } 611 } 612 613 // Check booking dates 614 if (typeof wpkStartDates != "undefined" && typeof wpkEndDates != "undefined") { 615 slots.isSecondSlotTaken = wpkIsDateInArray(date, wpkStartDates); 616 slots.isFirstSlotTaken = wpkIsDateInArray(date, wpkEndDates); 617 618 // Debug logging 619 620 } 621 622 return slots; 623 } 624 625 function wpkIsDateInArray(date, array) { 626 var dateString = date.format("YYYY-MM-DD"); 627 var isInArray = false;
628 629 // Check if array is object (with numeric keys) or regular array 630 if (Array.isArray(array)) { 631 isInArray = array.indexOf(dateString) !== -1; 632 } else if (typeof array === 'object') { 633 // Convert object values to array and check 634 var arrayValues = Object.values(array); 635 isInArray = arrayValues.indexOf(dateString) !== -1; 636 } 637 638 639 return isInArray; 640 } 641 642 let startDate = Cookies.get("listeo_rental_startdate"); 643 let endDate = Cookies.get("listeo_rental_enddate"); 644 let minSpan = $("#date-picker").data("minspan"); 645 let maxSpan = $("#date-picker").data("maxspan"); 646 let availableHours = []; 647 var is24Hour = Boolean(listeo_core.clockformat); 648 var timePickerIncrement = $("#date-picker").data("time-increment"); 649 //format: "M/DD hh:mm A"; 650 651 // if startDate exist and is not empty, set autoUpdateInput to true 652 let autoUpdateInput = true; 653 if(startDate === undefined) { 654 autoUpdateInput = false; 655 } 656 657 // Update the daterangepicker options to properly handle blocked dates 658 let daterangepicker_options = { 659 opens: "left", 660 autoUpdateInput: autoUpdateInput, 661 singleDatePicker: function() { 662 var listing_type = $("#date-picker").data("listing_type"); 663 var type_config = listeo_core.listing_types_config && listeo_core.listing_types_config[listing_type]; 664 // Use single date picker for non-rental types (events, services) 665 return type_config 666 ? type_config.supports_time_slots 667 : listing_type != "date_range"; 668 }(), 669 timePicker: $("#date-picker").data("rental-timepicker") == true ? true : false, 670 minDate: moment().subtract(0, "days"), 671 timePicker24Hour: is24Hour, 672 timePickerIncrement: timePickerIncrement, 673 minSpan: { days: minSpan }, 674 startDate: startDate ? startDate : moment(), 675 endDate: endDate ? endDate : moment().add(minSpan, "days"), 676 locale: { 677 format: $("#date-picker").data("rental-timepicker") == true 678 ? wordpress_date_format.date + " " + (is24Hour ? "HH:mm" : "hh:mm A") 679 : wordpress_date_format.date, 680 firstDay: parseInt(wordpress_date_format.day), 681 applyLabel: listeo_core.applyLabel, 682 cancelLabel: listeo_core.cancelLabel, 683 fromLabel: listeo_core.fromLabel, 684 toLabel: listeo_core.toLabel, 685 customRangeLabel: listeo_core.customRangeLabel, 686 daysOfWeek: [ 687 listeo_core.day_short_su, 688 listeo_core.day_short_mo, 689 listeo_core.day_short_tu, 690 listeo_core.day_short_we, 691 listeo_core.day_short_th, 692 listeo_core.day_short_fr, 693 listeo_core.day_short_sa, 694 ], 695 monthNames: [ 696 listeo_core.january, 697 listeo_core.february, 698 listeo_core.march, 699 listeo_core.april, 700 listeo_core.may, 701 listeo_core.june, 702 listeo_core.july, 703 listeo_core.august, 704 listeo_core.september, 705 listeo_core.october, 706 listeo_core.november, 707 listeo_core.december, 708 ], 709 }, 710 711 isCustomDate: function (date) { 712 if ($("#date-picker").data("rental-timepicker") == true) { 713 var dateString = date.format("YYYY-MM-DD"); 714 715 if (partialBookedDates && partialBookedDates[dateString]) { 716 var isFullyBooked = isDateFullyBooked(partialBookedDates[dateString]); 717 if (isFullyBooked) { 718 return "fully-booked"; 719 } 720 return "partially-booked"; 721 } 722 723 return "available"; 724 } else { 725 var slots = wpkGetThisDateSlots(date); 726 727 if (!slots.isFirstSlotTaken && !slots.isSecondSlotTaken) return []; 728 729 if (slots.isFirstSlotTaken && !slots.isSecondSlotTaken) { 730 return ["first-slot-taken"]; 731 } 732 733 if (slots.isSecondSlotTaken && !slots.isFirstSlotTaken) { 734 return ["second-slot-taken"]; 735 } 736 737 // If both slots are taken, return fully booked class 738 if (slots.isFirstSlotTaken && slots.isSecondSlotTaken) { 739 return ["fully-booked"]; 740 } 741 } 742 }, 743 744 isInvalidDate: function (date) { 745 // For event-based types, don't block any dates 746 var listing_type = $("#listing_type").val(); 747 var type_config = listeo_core.listing_types_config && listeo_core.listing_types_config[listing_type];
748 var is_event_type = type_config ? !type_config.supports_time_slots : (listing_type == "event"); // fallback 749 750 if (is_event_type) return false; 751 752 // For services with disabled dates 753 if ($("#listing_type").val() == "single_day" && typeof disabledDates !== "undefined") { 754 if (jQuery.inArray(date.format("YYYY-MM-DD"), disabledDates) !== -1) { 755 return true; 756 } 757 } 758 759 // Handle time slots for services 760 if ($(".time-slots-dropdown").length) { 761 var timeslotdays = $(".time-slots-dropdown").data("slots-days").toString(); 762 var timeslotdaysArray = timeslotdays.split(","); 763 764 if (timeslotdaysArray.includes(moment(date).day().toString())) { 765 return false; 766 } else { 767 return true; 768 } 769 } 770 771 // For rental listings 772 if ($("#listing_type").val() == "date_range") { 773 if ($("#date-picker").data("rental-timepicker") == true) { 774 var dateString = date.format("YYYY-MM-DD"); 775 776 // Check disabled dates 777 if ( 778 typeof disabledDates !== "undefined" && 779 disabledDates.indexOf(dateString) > -1 780 ) { 781 return true; 782 } 783 784 return false; 785 } else { 786 // Use the wpkGetThisDateSlots function to determine if date should be blocked 787 var slots = wpkGetThisDateSlots(date); 788 789 // Block dates where both slots are taken (fully booked) 790 return slots.isFirstSlotTaken && slots.isSecondSlotTaken; 791 } 792 } 793 794 return false; 795 }, 796 }; 797 798 $("#date-picker").daterangepicker(daterangepicker_options); 799 800 $("#date-picker").on("show.daterangepicker", function (ev, picker) { 801 $(".daterangepicker").addClass("calendar-visible calendar-animated"); 802 $(".daterangepicker").removeClass("calendar-hidden"); 803 }); 804 $("#date-picker").on("hide.daterangepicker", function (ev, picker) { 805 $(".daterangepicker").removeClass("calendar-visible"); 806 $(".daterangepicker").addClass("calendar-hidden"); 807 }); 808 809 // Handle date/time selection 810 $(".date-picker-listing-date_range").on( 811 "showCalendar.daterangepicker", 812 function (ev, picker) { 813 // Force update time picker whenever calendar is shown 814 if ($("#date-picker").data("rental-timepicker") == true) { 815 setTimeout(function () { 816 updateTimePicker(picker); 817 }, 50); 818 } 819 } 820 ); 821 822 // Handle time picker changes 823 $(".date-picker-listing-date_range").on( 824 "show.daterangepicker", 825 function (ev, picker) { 826 if ($("#date-picker").data("rental-timepicker") == true) { 827 // Initial time picker update 828 updateTimePicker(picker); 829 830 // Monitor for any changes to the time pickers 831 $(picker.container) 832 .find(".calendar-time") 833 .on("mousedown", function () { 834 // setTimeout(function () { 835 updateTimePicker(picker); 836 // }, 50); 837 }); 838 } 839 } 840 ); 841 842 $(".date-picker-listing-date_range").on( 843 "show.daterangepicker", 844 function (ev, picker) { 845 if ($("#date-picker").data("rental-timepicker") == true) { 846 setTimeout(function () { 847 // Add labels to calendar times if they don't exist 848 picker.container.find(".calendar-time").each(function (index) { 849 var $timeContainer = $(this); 850 if (!$timeContainer.prev().hasClass("calendar-time-label")) { 851 var label = 852 index === 0 853 ? listeo_core.start_time_label 854 : listeo_core.end_time_label; 855 $timeContainer.before( 856 '<div class="calendar-time-label">' + label + "</div>" 857 ); 858 } 859 }); 860 861 // Wrap selects in custom wrapper 862 }, 0); 863 } 864 } 865 ); 866 867 function updateTimePicker(picker) { 868 var startDate = picker.startDate.format("YYYY-MM-DD"); 869 var endDate = picker.endDate.format("YYYY-MM-DD"); 870 871 // Get available times for start and end dates 872 var startTimes = getAvailableTimes( 873 startDate, 874 partialBookedDates[startDate], 875 "check-in" 876 ); 877 var endTimes = getAvailableTimes( 878 endDate, 879 partialBookedDates[endDate], 880 "check-out" 881 ); 882 883 // Get the time selectors for both start and end 884 var startContainer = $(picker.container).find(".calendar-time").first(); 885 var endContainer = $(picker.container).find(".calendar-time").last(); 886 887 // Update start time selectors 888 updateTimeSelectors(startContainer, startTimes, picker.startDate); 889 890 // Update end time selectors 891 updateTimeSelectors(endContainer, endTimes, picker.endDate); 892 } 893 894 function updateTimeSelectors(container, availableTimes, currentDate) { 895 var hourSelect = container.find(".hourselect"); 896 var minuteSelect = container.find(".minuteselect"); 897 898 // Get unique available hours 899 var availableHours = [ 900 ...new Set(availableTimes.map((time) => parseInt(time.split(":")[0]))), 901 ].sort((a, b) => a - b); 902 903 // Clear and populate hour select 904 hourSelect.empty();
905 availableHours.forEach((hour) => { 906 hourSelect.append( 907 $("<option>", { 908 value: hour, 909 text: ("0" + hour).slice(-2), 910 }) 911 ); 912 }); 913 914 // If current hour is not in available hours, select first available 915 var currentHour = currentDate.hour(); 916 if (!availableHours.includes(currentHour)) { 917 hourSelect.val(availableHours[0]); 918 currentDate.hour(availableHours[0]); 919 } else { 920 hourSelect.val(currentHour); 921 } 922 923 // Update minutes for selected hour 924 updateMinutes(hourSelect, minuteSelect, availableTimes, currentDate); 925 926 // Add change handler for hours 927 hourSelect.off("change").on("change", function () { 928 updateMinutes(hourSelect, minuteSelect, availableTimes, currentDate); 929 }); 930 } 931 932 function updateMinutes( 933 hourSelect, 934 minuteSelect, 935 availableTimes, 936 currentDate 937 ) { 938 var selectedHour = parseInt(hourSelect.val()); 939 940 // Get available minutes for selected hour 941 var availableMinutes = availableTimes 942 .filter((time) => parseInt(time.split(":")[0]) === selectedHour) 943 .map((time) => parseInt(time.split(":")[1])) 944 .sort((a, b) => a - b); 945 946 // Clear and populate minute select 947 minuteSelect.empty(); 948 availableMinutes.forEach((minute) => { 949 minuteSelect.append( 950 $("<option>", { 951 value: minute, 952 text: ("0" + minute).slice(-2), 953 }) 954 ); 955 }); 956 957 // If current minute is not in available minutes, select first available 958 var currentMinute = currentDate.minute(); 959 if (!availableMinutes.includes(currentMinute)) { 960 minuteSelect.val(availableMinutes[0]); 961 currentDate.minute(availableMinutes[0]); 962 } else { 963 minuteSelect.val(currentMinute); 964 } 965 } 966 967 function getAvailableTimes(date, bookedTimes, type) { 968 var times = []; 969 var startHour = type === "check-in" ? 7 : 10; // Check-in starts at 9 AM 970 var endHour = type === "check-out" ? 20 : 17; // Check-out until 5 PM 971 972 // Generate times in 15-minute increments 973 for (var hour = startHour; hour <= endHour; hour++) { 974 for (var minute = 0; minute < 60; minute += timePickerIncrement) { 975 var timeStr = ("0" + hour).slice(-2) + ":" + ("0" + minute).slice(-2); 976 if (!isTimeBooked(timeStr, bookedTimes)) { 977 times.push(timeStr); 978 } 979 } 980 } 981 982 return times; 983 } 984 985 function isTimeBooked(timeStr, bookedTimes) { 986 if (!bookedTimes) return false; 987 988 return bookedTimes.some(function (booking) { 989 return ( 990 timeToMinutes(timeStr) >= timeToMinutes(booking.start) && 991 timeToMinutes(timeStr) <= timeToMinutes(booking.end) 992 ); 993 }); 994 } 995 996 function isDateFullyBooked(bookings) { 997 var timeBlocks = mergeTimeBlocks(bookings); 998 return timeBlocks.some(function (block) { 999 return block.start === "00:00" && block.end === "23:59"; 1000 }); 1001 } 1002 1003 function mergeTimeBlocks(bookings) { 1004 if (!bookings || !bookings.length) return []; 1005 1006 var sorted = bookings.sort(function (a, b) { 1007 return a.start.localeCompare(b.start); 1008 }); 1009 1010 var merged = []; 1011 var current = sorted[0]; 1012 1013 for (var i = 1; i < sorted.length; i++) { 1014 if (timeToMinutes(current.end) >= timeToMinutes(sorted[i].start)) { 1015 if (timeToMinutes(sorted[i].end) > timeToMinutes(current.end)) { 1016 current.end = sorted[i].end; 1017 } 1018 } else { 1019 merged.push(current); 1020 current = sorted[i]; 1021 } 1022 } 1023 merged.push(current); 1024 1025 return merged; 1026 } 1027 1028 function timeToMinutes(timeStr) { 1029 var parts = timeStr.split(":"); 1030 return parseInt(parts[0]) * 60 + parseInt(parts[1]); 1031 } 1032 1033 $(".date-picker-listing-date_range").on( 1034 "apply.daterangepicker", 1035 function (ev, picker) { 1036 if ($("#date-picker").data("rental-timepicker") == true) { 1037 if (picker.startDate && picker.endDate) { 1038 var timeFormat = is24Hour ? "HH:mm" : "hh:mm A"; 1039 var startFormatted = 1040 picker.startDate.format(wordpress_date_format.date) + 1041 " " + 1042 picker.startDate.format(timeFormat); 1043 var endFormatted = 1044 picker.endDate.format(wordpress_date_format.date) + 1045 " " + 1046 picker.endDate.format(timeFormat); 1047 $(this).val(startFormatted + " - " + endFormatted); 1048 } 1049 } else { 1050 $(this).val( 1051 picker.startDate.format(wordpress_date_format.date) + 1052 " - " + 1053 picker.endDate.format(wordpress_date_format.date) 1054 ); 1055 } 1056 // if it's other listing type, set date format 1057 } 1058 ); 1059 1060 $(".date-picker-listing-single_day").on( 1061 "apply.daterangepicker", 1062 function (ev, picker) { 1063 // For services we typically want just the selected date 1064 $(this).val(picker.startDate.format(wordpress_date_format.date)); 1065 1066 1067 } 1068 ); 1069 1070 function updateTimePicker(picker) { 1071 var startDate = picker.startDate.format("YYYY-MM-DD"); 1072 var endDate = picker.endDate.format("YYYY-MM-DD"); 1073 1074 var startTimes = getAvailableTimes( 1075 startDate, 1076 partialBookedDates[startDate], 1077 "check-in" 1078 ); 1079 var endTimes = getAvailableTimes( 1080 endDate, 1081 partialBookedDates[endDate], 1082 "check-out" 1083 ); 1084 1085 var startContainer = $(picker.container).find(".calendar-time").first(); 1086 var endContainer = $(picker.container).find(".calendar-time").last(); 1087 1088 updateTimeSelectors(startContainer, startTimes, picker.startDate); 1089 updateTimeSelectors(endContainer, endTimes, picker.endDate); 1090 } 1091 1092 function updateTimeSelectors(container, availableTimes, currentDate) { 1093 var is24Hour = Boolean(listeo_core.clockformat); 1094 var hourSelect = container.find(".hourselect"); 1095 var minuteSelect = container.find(".minuteselect"); 1096 var ampmSelect = container.find(".ampmselect"); 1097 1098 // Get current state 1099 var currentHour24 = currentDate.hour(); 1100 var currentMinute = currentDate.minute(); 1101 var isPM = currentHour24 >= 12; 1102 1103 function updateHourOptions() { 1104 var availableHours24 = [ 1105 ...new Set(availableTimes.map((time) => parseInt(time.split(":")[0]))), 1106 ].sort((a, b) => a - b); 1107 1108 // Filter hours based on AM/PM if in 12-hour mode 1109 if (!is24Hour) { 1110 var selectedIsPM = ampmSelect.val() === "PM"; 1111 availableHours24 = availableHours24.filter((hour) => { 1112 return selectedIsPM ? hour >= 12 : hour < 12; 1113 }); 1114 } 1115 1116 hourSelect.empty();
1117 availableHours24.forEach((hour24) => { 1118 var displayHour = is24Hour ? hour24 : hour24 % 12 || 12; 1119 hourSelect.append( 1120 $("<option>", { 1121 value: hour24, // Store 24-hour value 1122 text: ("0" + displayHour).slice(-2), 1123 }) 1124 ); 1125 }); 1126 1127 // Set appropriate hour 1128 if (availableHours24.length > 0) { 1129 var currentHourExists = availableHours24.includes(currentHour24); 1130 var hourToSet = currentHourExists ? currentHour24 : availableHours24[0]; 1131 hourSelect.val(hourToSet); 1132 currentDate.hour(hourToSet); 1133 return hourToSet; 1134 } 1135 return null; 1136 } 1137 1138 // Initial hour update 1139 var selectedHour24 = updateHourOptions(); 1140 1141 // Update minutes for the selected hour 1142 function updateMinutes(hour24) { 1143 if (hour24 === null) return; 1144 1145 var availableMinutes = availableTimes 1146 .filter((time) => parseInt(time.split(":")[0]) === hour24) 1147 .map((time) => parseInt(time.split(":")[1])) 1148 .sort((a, b) => a - b); 1149 1150 minuteSelect.empty(); 1151 availableMinutes.forEach((minute) => { 1152 minuteSelect.append( 1153 $("<option>", { 1154 value: minute, 1155 text: ("0" + minute).slice(-2), 1156 }) 1157 ); 1158 }); 1159 1160 // Set appropriate minute 1161 if (availableMinutes.length > 0) { 1162 if (availableMinutes.includes(currentMinute)) { 1163 minuteSelect.val(currentMinute); 1164 } else { 1165 minuteSelect.val(availableMinutes[0]); 1166 currentDate.minute(availableMinutes[0]); 1167 } 1168 } 1169 } 1170 1171 updateMinutes(selectedHour24); 1172 1173 // Event handlers 1174 if (!is24Hour) { 1175 ampmSelect.off("change").on("change", function () { 1176 var newHour24 = updateHourOptions(); 1177 updateMinutes(newHour24); 1178 }); 1179 } 1180 1181 hourSelect.off("change").on("change", function () { 1182 var selectedHour24 = parseInt($(this).val()); 1183 currentDate.hour(selectedHour24); 1184 updateMinutes(selectedHour24); 1185 }); 1186 1187 minuteSelect.off("change").on("change", function () { 1188 currentDate.minute(parseInt($(this).val())); 1189 }); 1190 1191 // Set initial AM/PM if in 12-hour mode 1192 if (!is24Hour) { 1193 ampmSelect.val(isPM ? "PM" : "AM"); 1194 } 1195 } 1196 1197 // Utility functions remain the same... 1198 function getAvailableTimes(date, bookedTimes, type) { 1199 var times = []; 1200 //if availableDays is not set, return empty array 1201 console.log(availableDays); 1202 if ( 1203 typeof availableDays === "undefined" || 1204 availableDays === null || 1205 availableDays === "" 1206 ) { 1207 var listingOpeningHours = []; 1208 } else { 1209 var listingOpeningHours = JSON.parse(availableDays); 1210 } 1211 1212 // Get day of week from the date (0 = Monday, 6 = Sunday) 1213 // var dayIndex = moment(date).day(); 1214 // dayIndex = dayIndex === 0 ? 6 : dayIndex - 1; 1215 var dayOfWeek = moment(date).isoWeekday(); // Returns 1-7 (Monday-Sunday) 1216 1217 // Get opening hours for this day 1218 var dayIndex = dayOfWeek - 1; 1219 // Get opening hours for this day 1220 var dayHours = listingOpeningHours[dayIndex]; 1221 console.log(dayHours); 1222 // If no hours set for this day or empty values, use default full day availability 1223 if ( 1224 !dayHours || 1225 !dayHours.opening || 1226 !dayHours.closing || 1227 dayHours.opening[0] === "" || 1228 dayHours.closing[0] === "" 1229 ) { 1230 // Use default hours based on check-in/check-out type 1231 // var defaultHours = { 1232 // opening: [(type === 'check-in' ? "09:00" : "10:00")], 1233 // closing: [(type === 'check-out' ? "20:00" : "17:00")] 1234 // }; 1235 1236 //dayHours = defaultHours; 1237 dayHours = { 1238 opening: ["00:00"], 1239 closing: ["23:59"], 1240 }; 1241 } 1242 1243 // Process each set of opening/closing hours 1244 for (var i = 0; i < dayHours.opening.length; i++) { 1245 var openTime = dayHours.opening[i]; 1246 var closeTime = dayHours.closing[i]; 1247 1248 if (!openTime || !closeTime) continue; 1249 1250 // Convert opening/closing times to hours and minutes 1251 var openHour = parseInt(openTime.split(":")[0]); 1252 var closeHour = parseInt(closeTime.split(":")[0]); 1253 var openMinute = parseInt(openTime.split(":")[1]); 1254 var closeMinute = parseInt(closeTime.split(":")[1]); 1255 1256 // // Apply check-in/check-out restrictions 1257 // if (type === 'check-in') { 1258 // // Don't allow check-in less than 1 hour before closing 1259 // var closeTimeInMinutes = closeHour * 60 + closeMinute; 1260 // closeTimeInMinutes -= 60; // Subtract 1 hour 1261 // closeHour = Math.floor(closeTimeInMinutes / 60); 1262 // closeMinute = closeTimeInMinutes % 60; 1263 // } else { // check-out 1264 // // Don't allow check-out before 10 AM or after 5 PM 1265 // if (openHour < 10) { 1266 // openHour = 10; 1267 // openMinute = 0; 1268 // } 1269 // if (closeHour > 17 || (closeHour === 17 && closeMinute > 0)) { 1270 // closeHour = 17; 1271 // closeMinute = 0; 1272 // } 1273 // } 1274 1275 // Generate available times within the valid range 1276 var currentHour = openHour; 1277 var currentMinute = 1278 currentHour === openHour 1279 ? Math.ceil(openMinute / timePickerIncrement) * timePickerIncrement 1280 : 0; 1281 1282 var endTimeInMinutes = closeHour * 60 + closeMinute; 1283 1284 while (currentHour * 60 + currentMinute <= endTimeInMinutes) { 1285 var timeStr = 1286 ("0" + currentHour).slice(-2) + ":" + ("0" + currentMinute).slice(-2); 1287 1288 if (!isTimeBooked(timeStr, bookedTimes)) { 1289 times.push(timeStr); 1290 } 1291 1292 currentMinute += timePickerIncrement; 1293 if (currentMinute >= 60) { 1294 currentMinute = 0; 1295 currentHour++; 1296 } 1297 } 1298 } 1299 1300 return times.sort(); 1301 } 1302 1303 function isTimeBooked(timeStr, bookedTimes) { 1304 if (!bookedTimes) return false;
1305 1306 return bookedTimes.some(function (booking) { 1307 return ( 1308 timeToMinutes(timeStr) >= timeToMinutes(booking.start) && 1309 timeToMinutes(timeStr) <= timeToMinutes(booking.end) 1310 ); 1311 }); 1312 } 1313 1314 function timeToMinutes(timeStr) { 1315 var parts = timeStr.split(":"); 1316 return parseInt(parts[0]) * 60 + parseInt(parts[1]); 1317 } 1318 1319 function updateSelectOptions($select, options) { 1320 // Clear existing options 1321 $select.empty(); 1322 1323 // Add new options 1324 options.forEach(function (option) { 1325 $select.append( 1326 $("<option>", { 1327 value: option.value, 1328 text: option.text, 1329 }) 1330 ); 1331 }); 1332 1333 // Refresh bootstrap-select 1334 $select.selectpicker("refresh"); 1335 } 1336 1337 // $(".date-picker-listing-date_range").on( 1338 // "apply.daterangepicker", 1339 // function (ev, picker) { 1340 // var startDate = picker.startDate.format("YYYY-MM-DD"); 1341 // var endDate = picker.endDate.format("YYYY-MM-DD"); 1342 1343 // // Check if selected dates include any partial bookings 1344 // var hasPartialBookings = false; 1345 // var current = moment(startDate); 1346 // var end = moment(endDate); 1347 1348 // while (current <= end) { 1349 // var dateString = current.format("YYYY-MM-DD"); 1350 // if (partialBookedDates && partialBookedDates[dateString]) { 1351 // hasPartialBookings = true; 1352 // break; 1353 // } 1354 // current.add(1, "days"); 1355 // } 1356 1357 // if (hasPartialBookings) { 1358 // // Show time selection dialog 1359 // showTimeSelectionDialog(startDate, endDate, partialBookedDates); 1360 // } 1361 1362 // $(this).val( 1363 // picker.startDate.format(listeo_core.date_format) + 1364 // " - " + 1365 // picker.endDate.format(listeo_core.date_format) 1366 // ); 1367 // } 1368 // ); 1369 1370 // $("#date-picker").on("apply.daterangepicker", function (ev, picker) { 1371 // const listing_id = $("#listing_id").val(); 1372 // const startDate = picker.startDate.format("YYYY-MM-DD HH:mm:ss"); 1373 // const endDate = picker.endDate.format("YYYY-MM-DD HH:mm:ss"); 1374 // $(".date-picker-listing-date_range").removeClass("add-slot-shake-error"); 1375 // $.ajax({ 1376 // url: listeo.ajaxurl, 1377 // type: "POST", 1378 // data: { 1379 // action: "check_date_range_availability", 1380 // listing_id: listing_id, 1381 // start_date: startDate, 1382 // end_date: endDate, 1383 // }, 1384 // success: function (response) { 1385 // if (response.success) { 1386 // if (response.data.available) { 1387 // check_booking(); 1388 // // wait 2s and fadeOut .booking-notice-message 1389 // setTimeout(function () { 1390 // $(".booking-notice-message").fadeOut(); 1391 // }, 2000); 1392 // } else { 1393 // if (response.data.next_available) { 1394 // // Parse dates from response 1395 // const newStartDate = moment(response.data.next_available.start); 1396 // const newEndDate = moment(response.data.next_available.end); 1397 // $(".date-picker-listing-date_range").addClass("add-slot-shake-error"); 1398 // $(".booking-notice-message") 1399 // .html( 1400 // "Selected time is not available.\n\n" + 1401 // "Next available time is:\n" + 1402 // newStartDate.format("DD-MM-YYYY HH:mm") + 1403 // " to " + 1404 // newEndDate.format("DD-MM-YYYY HH:mm") 1405 // ) 1406 // .fadeIn(); 1407 1408 // // Update the date picker with new dates 1409 // picker.setStartDate(newStartDate); 1410 // picker.setEndDate(newEndDate); 1411 1412 // // Optional: Trigger the date picker to update its display 1413 // $(picker.element).data("daterangepicker").updateView(); 1414 // $(picker.element).data("daterangepicker").updateCalendars(); 1415 // } else { 1416 // alert(response.data.message || "No available time slots found."); 1417 // } 1418 // } 1419 // } 1420 // }, 1421 // error: function () { 1422 // alert("There was an error checking availability. Please try again."); 1423 // }, 1424 // }); 1425 // }); 1426 1427 // // When time is selected 1428 // $("#date-picker").on("apply.daterangepicker", function (ev, picker) { 1429 // const selectedDateTime = picker.startDate; 1430 // let isValidSelection = false;
1431 1432 // if (availableHours && availableHours.length > 0) { 1433 // for (let slot of availableHours) { 1434 // const slotStart = moment(slot.start); 1435 // const slotEnd = moment(slot.end); 1436 1437 // if (selectedDateTime.isBetween(slotStart, slotEnd, undefined, "[]")) { 1438 // isValidSelection = true; 1439 // break; 1440 // } 1441 // } 1442 1443 // if (!isValidSelection) { 1444 // // Find first available time 1445 // const firstSlot = availableHours[0]; 1446 // if (firstSlot) { 1447 // picker.setStartDate(moment(firstSlot.start)); 1448 // alert( 1449 // "Selected time is not available. Moving to first available time." 1450 // ); 1451 // } 1452 // return false; 1453 // } 1454 // } 1455 1456 // // If we get here, proceed with booking check 1457 // check_booking(); 1458 // }); 1459 function calculate_price() { 1460 var ajax_data = { 1461 action: "calculate_price", 1462 listing_type: $("#date-picker").data("listing_type"), 1463 listing_id: $("input#listing_id").val(), 1464 tickets: $("input#tickets").val(), 1465 coupon: $("input#coupon_code").val(), 1466 //'nonce': nonce 1467 }; 1468 1469 // Add children count if present 1470 if ($(".qtyButtons.children input").length) { 1471 ajax_data.children = $(".qtyButtons.children input").val(); 1472 } 1473 1474 // Add pets count if present 1475 if ($(".qtyButtons.animals input").length) { 1476 ajax_data.animals = $(".qtyButtons.animals input").val(); 1477 } 1478 var services = []; 1479 1480 $.each($("input.bookable-service-checkbox:checked"), function () { 1481 var quantity = $(this) 1482 .parent() 1483 .find("input.bookable-service-quantity") 1484 .val(); 1485 services.push({ service: $(this).val(), value: quantity }); 1486 }); 1487 ajax_data.services = services; 1488 $.ajax({ 1489 type: "POST", 1490 dataType: "json", 1491 url: listeo.ajaxurl, 1492 data: ajax_data, 1493 1494 success: function (data) { 1495 $("#negative-feedback").fadeOut(); 1496 $("a.book-now").removeClass("inactive"); 1497 if (data.data.price) { 1498 if (listeo_core.currency_position == "before") { 1499 $(".booking-estimated-cost span").html( 1500 listeo_core.currency_symbol + " " + data.data.price 1501 ); 1502 } else { 1503 $(".booking-estimated-cost span").html( 1504 data.data.price + " " + listeo_core.currency_symbol 1505 ); 1506 } 1507 $(".booking-estimated-cost").data("price", data.data.price); 1508 $(".booking-estimated-cost").fadeIn(); 1509 } 1510 if (data.data.price_discount) { 1511 if (listeo_core.currency_position == "before") { 1512 $(".booking-estimated-discount-cost span").html( 1513 listeo_core.currency_symbol + " " + data.data.price_discount 1514 ); 1515 } else { 1516 $(".booking-estimated-discount-cost span").html( 1517 data.data.price_discount + " " + listeo_core.currency_symbol 1518 ); 1519 } 1520 $(".booking-estimated-cost").addClass("estimated-with-discount"); 1521 $(".booking-estimated-discount-cost").fadeIn(); 1522 } else { 1523 $(".booking-estimated-cost").removeClass("estimated-with-discount"); 1524 $(".booking-estimated-discount-cost").fadeOut(); 1525 } 1526 }, 1527 }); 1528 } 1529 1530 function calculate_booking_form_price() { 1531 var ajax_data = { 1532 action: "listeo_calculate_booking_form_price", 1533 coupon: $("input#coupon_code").val(), 1534 price: $("li.total-costs").data("price"), 1535 }; 1536 1537 $.ajax({ 1538 type: "POST", 1539 dataType: "json", 1540 url: listeo.ajaxurl, 1541 data: ajax_data, 1542 1543 success: function (data) { 1544 if (data.price >= 0) { 1545 if (listeo_core.currency_position == "before") { 1546 $(".total-discounted_costs span").html( 1547 listeo_core.currency_symbol + " " + data.price 1548 ); 1549 } else { 1550 $(".total-discounted_costs span").html( 1551 data.price + " " + listeo_core.currency_symbol 1552 ); 1553 } 1554 1555 $(".total-discounted_costs").fadeIn(); 1556 $(".total-costs").addClass("estimated-with-discount"); 1557 } else { 1558 $(".total-discounted_costs ").fadeOut(); 1559 $(".total-costs").removeClass("estimated-with-discount"); 1560 } 1561 }, 1562 }); 1563 } 1564 1565 // function when checking booking by widget 1566 function check_booking() { 1567 inputClicked = true; 1568 if (is_open === false) return 0; 1569 1570 // if we not deal with services with slots or opening hours 1571 if ( 1572 $("#date-picker").data("listing_type") == "service" && 1573 !$("input#slot").val() && 1574 !$(".time-picker").val() 1575 ) { 1576 $("#negative-feedback").fadeIn(); 1577 1578 return; 1579 } 1580 1581 if ($("#date-picker").data("rental-timepicker")) { 1582 var timeFormat = is24Hour ? "HH:mm" : "hh:mm A"; 1583 Cookies.set( 1584 "listeo_rental_picker_startdate", 1585 $("#date-picker") 1586 .data("daterangepicker") 1587 .startDate.format(wordpress_date_format.date + " " + timeFormat) 1588 ); 1589 Cookies.set( 1590 "listeo_rental_picker_enddate", 1591 $("#date-picker") 1592 .data("daterangepicker") 1593 .endDate.format(wordpress_date_format.date + " " + timeFormat) 1594 ); 1595 // var startDataSQL need to have hour 1596 var startDataSql = moment( 1597 $("#date-picker").data("daterangepicker").startDate, 1598 ["MM/DD/YYYY " + timeFormat] 1599 ).format("YYYY-MM-DD HH:mm:ss"); 1600 var endDataSql = moment( 1601 $("#date-picker").data("daterangepicker").endDate, 1602 ["MM/DD/YYYY " + timeFormat] 1603 ).format("YYYY-MM-DD HH:mm:ss"); 1604 } else { 1605 Cookies.set( 1606 "listeo_rental_startdate", 1607 $("#date-picker") 1608 .data("daterangepicker") 1609 .startDate.format(wordpress_date_format.date) 1610 ); 1611 Cookies.set( 1612 "listeo_rental_enddate", 1613 $("#date-picker") 1614 .data("daterangepicker") 1615 .endDate.format(wordpress_date_format.date) 1616 ); 1617 var startDataSql = moment( 1618 $("#date-picker").data("daterangepicker").startDate, 1619 ["MM/DD/YYYY"] 1620 ).format("YYYY-MM-DD"); 1621 var endDataSql = moment( 1622 $("#date-picker").data("daterangepicker").endDate, 1623 ["MM/DD/YYYY"] 1624 ).format("YYYY-MM-DD"); 1625 } 1626 1627 //console.log($('#date-picker').data('daterangepicker').startDate); 1628 1629 // preparing data for ajax 1630 var ajax_data = { 1631 action: "check_avaliabity", 1632 listing_type: $("#date-picker").data("listing_type"), 1633 listing_id: $("input#listing_id").val(), 1634 coupon: $("input#coupon_code").val(), 1635 date_start: startDataSql, 1636 date_end: endDataSql, 1637 //'nonce': nonce 1638 }; 1639 var services = []; 1640 // $.each($("input.bookable-service-checkbox:checked"), function(){ 1641 // services.push($(this).val()); 1642 // }); 1643 // $.each($("input.bookable-service-quantity"), function(){ 1644 // services.push($(this).val()); 1645 // }); 1646 $.each($("input.bookable-service-checkbox:checked"), function () { 1647 var quantity = $(this) 1648 .parent() 1649 .find("input.bookable-service-quantity") 1650 .val(); 1651 services.push({ service: $(this).val(), value: quantity }); 1652 }); 1653 1654 ajax_data.services = services; 1655 1656 if ($("input#slot").val()) ajax_data.slot = $("input#slot").val(); 1657 if ($("input.adults").val()) ajax_data.adults = $("input.adults").val(); 1658 1659 if ($(".time-picker").val()) ajax_data.hour = $(".time-picker").val(); 1660 if ($(".time-picker-end-hour").val()) 1661 ajax_data.end_hour = $(".time-picker-end-hour").val(); 1662 1663 // Add children count if present 1664 if ($(".qtyButtons input.children ").length) { 1665 ajax_data.children = $(".qtyButtons input.children ").val(); 1666 } 1667 1668 // Add animals count if present 1669 if ($(".qtyButtons input.animals ").length) { 1670 ajax_data.animals = $(".qtyButtons input.animals").val(); 1671 } 1672 1673 // Add tickets count if present (for event/ticket listings) 1674 if ($("input#tickets").length) { 1675 ajax_data.tickets = $("input#tickets").val(); 1676 } 1677 1678 // loader class 1679 $("a.book-now").addClass("loading"); 1680 $("a.book-now-notloggedin").addClass("loading"); 1681 1682 $.ajax({ 1683 type: "POST", 1684 dataType: "json",
1685 url: listeo.ajaxurl, 1686 data: ajax_data, 1687 1688 success: function (data) { 1689 var default_error_message = $(".booking-error-message").data("default-message"); 1690 $(".booking-error-message").html(default_error_message); 1691 // loader clas 1692 if ( 1693 data.success == true && 1694 (!$(".time-picker").length || is_open != false) 1695 ) { 1696 if (data.data.free_places > 0) { 1697 $("a.book-now,a.book-now-notloggedin").data( 1698 "freeplaces", 1699 data.data.free_places 1700 ); 1701 // if date picker doesn't have daat attribute rental-timepicker 1702 1703 $(".booking-error-message").fadeOut(); 1704 1705 $("a.book-now").removeClass("inactive"); 1706 if (data.data.price) { 1707 if (listeo_core.currency_position == "before") { 1708 $(".booking-estimated-cost span").html( 1709 listeo_core.currency_symbol + " " + data.data.price 1710 ); 1711 } else { 1712 $(".booking-estimated-cost span").html( 1713 data.data.price + " " + listeo_core.currency_symbol 1714 ); 1715 } 1716 $(".booking-estimated-cost").data("price", data.data.price); 1717 $(".booking-estimated-cost").fadeIn(); 1718 } else { 1719 $(".booking-estimated-cost span").html( 1720 "0 " + listeo_core.currency_symbol 1721 ); 1722 $(".booking-estimated-cost").fadeOut(); 1723 } 1724 if (data.data.price_discount) { 1725 if (listeo_core.currency_position == "before") { 1726 $(".booking-estimated-discount-cost span").html( 1727 listeo_core.currency_symbol + " " + data.data.price_discount 1728 ); 1729 } else { 1730 $(".booking-estimated-discount-cost span").html( 1731 data.data.price_discount + " " + listeo_core.currency_symbol 1732 ); 1733 } 1734 $(".booking-estimated-cost").addClass("estimated-with-discount"); 1735 $(".booking-estimated-discount-cost").fadeIn(); 1736 } else { 1737 $(".booking-estimated-cost").removeClass( 1738 "estimated-with-discount" 1739 ); 1740 $(".booking-estimated-discount-cost").fadeOut(); 1741 } 1742 validate_coupon($("input#listing_id").val(), data.data.price); 1743 $(".coupon-widget-wrapper").fadeIn(); 1744 1745 // Auto-submit for event/ticket listings (one-click booking) 1746 // Check if this is a ticket/event listing by presence of tickets input 1747 if ($("input#tickets").length) { 1748 // For events, auto-submit the booking after successful availability check 1749 var listing_type = $("#date-picker").data("listing_type") || $("#listing_type").val(); 1750 var booking_data = { 1751 listing_type: listing_type, 1752 listing_id: $("#listing_id").val() || $("input#listing_id").val(), 1753 date_start: ajax_data.date_start, 1754 date_end: ajax_data.date_end, 1755 tickets: $("#tickets").val() 1756 }; 1757 1758 if ($("#coupon_code").val()) booking_data.coupon = $("#coupon_code").val(); 1759 1760 // Add services 1761 if (ajax_data.services && ajax_data.services.length > 0) { 1762 booking_data.services = ajax_data.services; 1763 } 1764 1765 $("input#booking").val(JSON.stringify(booking_data)); 1766 $("#form-booking").submit(); 1767 } 1768 } else { 1769 $("a.book-now,a.book-now-notloggedin").data("freeplaces", 0); 1770 // if data.data.errro exists, set the message to .booking-error-message 1771 1772 // if (!$("#date-picker").data("rental-timepicker")) { 1773 // $(".booking-error-message").fadeIn(); 1774 // } 1775 $(".booking-error-message").fadeIn(); 1776 $(".booking-estimated-cost").fadeOut(); 1777 1778 $(".booking-estimated-cost span").html(""); 1779 } 1780 } else { 1781 // if (!$("#date-picker").data("rental-timepicker")) { 1782 // $("a.book-now,a.book-now-notloggedin").data("freeplaces", 0); 1783 // $(".booking-error-message").fadeIn(); 1784 // } 1785 1786 if (data.error) { 1787 $(".booking-error-message").html(data.error); 1788 } 1789 1790 $(".booking-error-message").fadeIn(); 1791 $(".booking-estimated-cost").fadeOut(); 1792 } 1793 $("a.book-now").removeClass("loading"); 1794 $("a.book-now-notloggedin").removeClass("loading"); 1795 }, 1796 }); 1797 } 1798 1799 var is_open = true; 1800 var lastDayOfWeek; 1801 1802 // update slots and check hours setted to this day 1803 function update_booking_widget() { 1804 // function only for services 1805 if ($("#date-picker").data("listing_type") != "single_day") return; 1806 1807 $("a.book-now").addClass("loading"); 1808 $("a.book-now-notloggedin").addClass("loading"); 1809 // get day of week 1810 1811 var date = $("#date-picker").data("daterangepicker").endDate._d; 1812 var dayOfWeek = date.getDay() - 1; 1813 1814 if (date.getDay() == 0) { 1815 dayOfWeek = 6; 1816 } 1817 1818 var startDataSql = moment( 1819 $("#date-picker").data("daterangepicker").startDate, 1820 ["MM/DD/YYYY"] 1821 ).format("YYYY-MM-DD"); 1822 var endDataSql = moment($("#date-picker").data("daterangepicker").endDate, [ 1823 "MM/DD/YYYY", 1824 ]).format("YYYY-MM-DD"); 1825 1826 var ajax_data = { 1827 action: "update_slots", 1828 listing_id: $("input#listing_id").val(), 1829 date_start: startDataSql, 1830 date_end: endDataSql, 1831 slot: dayOfWeek, 1832 //'nonce': nonce 1833 }; 1834 1835 $.ajax({ 1836 type: "POST", 1837 dataType: "json",
1838 url: listeo.ajaxurl, 1839 data: ajax_data, 1840 1841 success: function (data) { 1842 $(".time-slots-dropdown .panel-dropdown-scrollable").html(data.data); 1843 1844 // reset values of slot selector 1845 if (dayOfWeek != lastDayOfWeek) { 1846 $(".panel-dropdown-scrollable .time-slot input").prop( 1847 "checked", 1848 false 1849 ); 1850 1851 $(".panel-dropdown.time-slots-dropdown input#slot").val(""); 1852 $(".panel-dropdown.time-slots-dropdown a").html( 1853 $(".panel-dropdown.time-slots-dropdown a").attr("placeholder") 1854 ); 1855 $(" .booking-estimated-cost span").html(" "); 1856 } 1857 1858 lastDayOfWeek = dayOfWeek; 1859 1860 if ( 1861 !$(".panel-dropdown-scrollable .time-slot[day='" + dayOfWeek + "']") 1862 .length 1863 ) { 1864 $(".no-slots-information").show(); 1865 $(".panel-dropdown.time-slots-dropdown a").html( 1866 $(".no-slots-information").html() 1867 ); 1868 } else { 1869 // when we dont have slots for this day reset cost and show no slots 1870 $(".no-slots-information").hide(); 1871 $(".panel-dropdown.time-slots-dropdown a").html( 1872 $(".panel-dropdown.time-slots-dropdown a").attr("placeholder") 1873 ); 1874 $(" .booking-estimated-cost span").html(" "); 1875 } 1876 // show only slots for this day 1877 $(".panel-dropdown-scrollable .time-slot").hide(); 1878 1879 $( 1880 ".panel-dropdown-scrollable .time-slot[day='" + dayOfWeek + "']" 1881 ).show(); 1882 $(".time-slot").each(function () { 1883 var timeSlot = $(this); 1884 $(this) 1885 .find("input") 1886 .on("change", function () { 1887 var timeSlotVal = timeSlot.find("strong").text(); 1888 var slotArray = [ 1889 timeSlot.find("strong").text(), 1890 timeSlot.find("input").val(), 1891 ]; 1892 1893 $(".panel-dropdown.time-slots-dropdown input#slot").val( 1894 JSON.stringify(slotArray) 1895 ); 1896 1897 $(".panel-dropdown.time-slots-dropdown a").html(timeSlotVal); 1898 1899 $(".panel-dropdown").removeClass("active"); 1900 1901 check_booking(); 1902 }); 1903 }); 1904 $("a.book-now").removeClass("loading"); 1905 $("a.book-now-notloggedin").removeClass("loading"); 1906 }, 1907 }); 1908 1909 // check if opening days are active 1910 if ($(".time-picker").length) { 1911 if (availableDays) { 1912 var availableDays = JSON.parse(availableDays); 1913 1914 if ( 1915 availableDays[dayOfWeek].opening == "Closed" || 1916 availableDays[dayOfWeek].closing == "Closed" 1917 ) { 1918 $("#negative-feedback").fadeIn(); 1919 1920 //$('a.book-now').css('background-color','grey'); 1921 1922 is_open = false; 1923 //console.log('zamkniete tego dnia' + dayOfWeek); 1924 return; 1925 } 1926 1927 // converent hours to 24h format 1928 var opening_hour = moment(availableDays[dayOfWeek].opening, [ 1929 "h:mm A", 1930 ]).format("HH:mm"); 1931 var closing_hour = moment(availableDays[dayOfWeek].closing, [ 1932 "h:mm A", 1933 ]).format("HH:mm"); 1934 1935 // get hour in 24 format 1936 var current_hour = $(".time-picker").val(); 1937 1938 // check if currer hour bar is open 1939 if (current_hour >= opening_hour && current_hour <= closing_hour) { 1940 is_open = true; 1941 $("#negative-feedback").fadeOut(); 1942 $("a.book-now").attr("href", "#").css("background-color", "#f30c0c"); 1943 check_booking(); 1944 //console.log('otwarte' + dayOfWeek); 1945 } else { 1946 is_open = false; 1947 $("#negative-feedback").fadeIn(); 1948 //$('a.book-now').attr('href','#').css('background-color','grey'); 1949 $(".booking-estimated-cost span").html(""); 1950 //console.log('zamkniete'); 1951 } 1952 } else { 1953 check_booking(); 1954 } 1955 } 1956 } 1957 1958 // if slots exist update them 1959 if ($(".time-slot").length) { 1960 1961 update_booking_widget(); 1962 } 1963 1964 // show only services for actual day from datapicker 1965 $("#date-picker").on("apply.daterangepicker", update_booking_widget); 1966 $("#date-picker").on("change", function () { 1967 1968 check_booking(); 1969 // update_booking_widget(); 1970 }); 1971 1972 // when slot is selected check if there are avalible bookings
1973 $("#date-picker").on("apply.daterangepicker", check_booking); 1974 $("#date-picker").on("cancel.daterangepicker", check_booking); 1975 1976 $(document).on( 1977 "change", 1978 "input#slot,input.adults,.panel-with-children input, input.bookable-service-quantity, .form-booking-single_day input.bookable-service-checkbox,.form-booking-date_range input.bookable-service-checkbox", 1979 function (event) { 1980 check_booking(); 1981 } 1982 ); 1983 //$('input#slot').on( 'change', check_booking ); 1984 1985 $("input#tickets,.form-booking-tickets input.bookable-service-checkbox").on( 1986 "change", 1987 function (e) { 1988 //check_booking(); 1989 calculate_price(); 1990 } 1991 ); 1992 1993 // hours picker 1994 if ($(".time-picker").length) { 1995 var time24 = false; 1996 1997 if (listeo_core.clockformat) { 1998 time24 = true; 1999 } 2000 const calendars = $(".time-picker").flatpickr({ 2001 enableTime: true, 2002 noCalendar: true, 2003 dateFormat: "H:i", 2004 time_24hr: time24, 2005 disableMobile: "true", 2006 minuteIncrement: timePickerIncrement, 2007 2008 // check if there are free days on change and calculate price 2009 onClose: function (selectedDates, dateStr, instance) { 2010 update_booking_widget(); 2011 check_booking(); 2012 }, 2013 }); 2014 2015 if ($("#_hour_end").length) { 2016 calendars[0].config.onClose = [ 2017 () => { 2018 setTimeout(() => calendars[1].open(), 1); 2019 }, 2020 ]; 2021 2022 calendars[0].config.onChange = [ 2023 (selDates) => { 2024 calendars[1].set("minDate", selDates[0]); 2025 }, 2026 ]; 2027 // calendars[0].config.onChange = [ 2028 // (selDates) => { 2029 // if (selDates[0]) { 2030 // let minEndDate = new Date(selDates[0]); 2031 // minEndDate.setHours(minEndDate.getHours() + 4); 2032 // calendars[1].set("minDate", minEndDate); 2033 // } 2034 // }, 2035 // ]; 2036 2037 calendars[1].config.onChange = [ 2038 (selDates) => { 2039 calendars[0].set("maxDate", selDates[0]); 2040 }, 2041 ]; 2042 } 2043 } 2044 2045 /*----------------------------------------------------*/ 2046 /* Bookings Dashboard Script 2047/*----------------------------------------------------*/ 2048 $(".booking-services").on("click", ".qtyInc", function () { 2049 var $button = $(this); 2050 2051 var oldValue = $button.parent().find("input").val(); 2052 //console.log(oldValue); 2053 if (oldValue == 2) { 2054 //$button.parents('.single-service').find('label').trigger('click'); 2055 $button 2056 .parents(".single-service") 2057 .find("input.bookable-service-checkbox") 2058 .prop("checked", true); 2059 updateCounter(); 2060 } 2061 if ($("#form-booking").hasClass("form-booking-tickets")) { 2062 calculate_price(); 2063 } else { 2064 check_booking(); 2065 } 2066 //calculate_price(); 2067 }); 2068 2069 if ($("#booking-date-range").length) { 2070 // to update view with bookin 2071 2072 var bookingsOffset = 0; 2073 2074 // here we can set how many bookings per page 2075 var bookingsLimit = 5; 2076 2077 // function when checking booking by widget 2078 function listeo_bookings_manage(page) { 2079 if ($("#booking-date-range").data("daterangepicker")) { 2080 var startDataSql = moment( 2081 $("#booking-date-range").data("daterangepicker").startDate, 2082 ["MM/DD/YYYY"] 2083 ).format("YYYY-MM-DD"); 2084 var endDataSql = moment( 2085 $("#booking-date-range").data("daterangepicker").endDate, 2086 ["MM/DD/YYYY"] 2087 ).format("YYYY-MM-DD"); 2088 } else { 2089 var startDataSql = ""; 2090 var endDataSql = ""; 2091 } 2092 if (!page) { 2093 page = 1; 2094 } 2095 if (page == "prev") { 2096 var current = $("div.pagination-container li.current").data("paged"); 2097 page = current - 1; 2098 } 2099 if (page == "next") { 2100 var current = $("div.pagination-container li.current").data("paged"); 2101 page = current + 1; 2102 } 2103 2104 // preparing data for ajax 2105 var ajax_data = { 2106 action: "listeo_bookings_manage", 2107 date_start: startDataSql, 2108 date_end: endDataSql, 2109 listing_id: $("#listing_id").val(), 2110 listing_status: $("#listing_status").val(), 2111 booking_author: $("#booking_author").val(), 2112 dashboard_type: $("#dashboard_type").val(), 2113 limit: bookingsLimit, 2114 offset: bookingsOffset, 2115 page: page, 2116 //'nonce': nonce 2117 }; 2118 2119 // display loader class 2120 $(".dashboard-list-box").addClass("loading"); 2121 2122 $.ajax({ 2123 type: "POST", 2124 dataType: "json",
2125 url: listeo.ajaxurl, 2126 data: ajax_data, 2127 2128 success: function (data) { 2129 // display loader class 2130 $(".dashboard-list-box").removeClass("loading"); 2131 2132 if (data.data.html) { 2133 $("#no-bookings-information").hide(); 2134 $("ul#booking-requests").html(data.data.html); 2135 $(".pagination-container").html(data.data.pagination); 2136 } else { 2137 $("ul#booking-requests").empty(); 2138 $(".pagination-container").empty(); 2139 $("#no-bookings-information").show(); 2140 } 2141 }, 2142 }); 2143 } 2144 2145 $("#dashboard-booking-author-search").autocomplete({ 2146 source: function (req, response) { 2147 $.getJSON( 2148 listeo_core.ajax_url + 2149 "?callback=?&action=listeo_core_incremental_listing_suggest", 2150 req, 2151 response 2152 ); 2153 }, 2154 select: function (event, ui) { 2155 window.location.href = ui.item.link; 2156 }, 2157 minLength: 3, 2158 }); 2159 2160 // hooks for get bookings into view 2161 $("#booking-date-range").on("apply.daterangepicker", function (e) { 2162 listeo_bookings_manage(); 2163 }); 2164 $("#listing_id").on("change", function (e) { 2165 listeo_bookings_manage(); 2166 }); 2167 $("#listing_status").on("change", function (e) { 2168 listeo_bookings_manage(); 2169 }); 2170 $("#booking_author").on("change", function (e) { 2171 listeo_bookings_manage(); 2172 }); 2173 2174 $("div.pagination-container").on("click", "a", function (e) { 2175 e.preventDefault(); 2176 2177 var page = $(this).parent().data("paged"); 2178 2179 listeo_bookings_manage(page); 2180 2181 $("body, html").animate( 2182 { 2183 scrollTop: $(".dashboard-list-box").offset().top, 2184 }, 2185 600 2186 ); 2187 2188 return false; 2189 }); 2190 2191 $(document).on("click", ".reject, .cancel", function (e) { 2192 e.preventDefault(); 2193 if (window.confirm(listeo_core.areyousure)) { 2194 var $this = $(this); 2195 $this.parents("li").addClass("loading"); 2196 var status = "confirmed"; 2197 if ($(this).hasClass("reject")) status = "cancelled"; 2198 if ($(this).hasClass("cancel")) status = "cancelled"; 2199 2200 // preparing data for ajax 2201 var ajax_data = { 2202 action: "listeo_bookings_manage", 2203 booking_id: $(this).data("booking_id"), 2204 status: status, 2205 //'nonce': nonce 2206 }; 2207 $.ajax({ 2208 type: "POST", 2209 dataType: "json", 2210 url: listeo.ajaxurl, 2211 data: ajax_data, 2212 2213 success: function (data) { 2214 // display loader class 2215 $this.parents("li").removeClass("loading"); 2216 2217 listeo_bookings_manage(); 2218 }, 2219 }); 2220 } 2221 }); 2222 2223 $(document).on("click", ".delete", function (e) { 2224 e.preventDefault(); 2225 if (window.confirm(listeo_core.areyousure)) { 2226 var $this = $(this); 2227 $this.parents("li").addClass("loading"); 2228 var status = "deleted"; 2229 2230 // preparing data for ajax 2231 var ajax_data = { 2232 action: "listeo_bookings_manage", 2233 booking_id: $(this).data("booking_id"), 2234 status: status, 2235 //'nonce': nonce 2236 }; 2237 $.ajax({ 2238 type: "POST", 2239 dataType: "json", 2240 url: listeo.ajaxurl, 2241 data: ajax_data, 2242 2243 success: function (data) { 2244 // display loader class 2245 $this.parents("li").removeClass("loading"); 2246 2247 listeo_bookings_manage(); 2248 }, 2249 }); 2250 } 2251 }); 2252 2253 $(document).on("click", ".renew_booking", function (e) { 2254 e.preventDefault(); 2255 if (window.confirm(listeo_core.areyousure)) { 2256 var $this = $(this); 2257 $this.parents("li").addClass("loading"); 2258 2259 // preparing data for ajax 2260 var ajax_data = { 2261 action: "listeo_bookings_renew_booking", 2262 booking_id: $(this).data("booking_id"), 2263 //'nonce': nonce 2264 }; 2265 $.ajax({ 2266 type: "POST", 2267 dataType: "json", 2268 url: listeo.ajaxurl, 2269 data: ajax_data, 2270 2271 success: function (data) { 2272 if (data.success) { 2273 } else { 2274 alert(listeo_core.booked_dates); 2275 } 2276 // display loader class 2277 $this.parents("li").removeClass("loading"); 2278 2279 listeo_bookings_manage(); 2280 }, 2281 }); 2282 } 2283 }); 2284 2285 $(document).on("click", ".approve", function (e) { 2286 e.preventDefault(); 2287 var $this = $(this); 2288 $this.parents("li").addClass("loading"); 2289 var status = "confirmed"; 2290 if ($(this).hasClass("reject")) status = "cancelled"; 2291 if ($(this).hasClass("cancel")) status = "cancelled"; 2292 2293 // preparing data for ajax 2294 var ajax_data = { 2295 action: "listeo_bookings_manage", 2296 booking_id: $(this).data("booking_id"), 2297 status: status, 2298 //'nonce': nonce 2299 }; 2300 $.ajax({ 2301 type: "POST", 2302 dataType: "json",
2303 url: listeo.ajaxurl, 2304 data: ajax_data, 2305 2306 success: function (data) { 2307 // display loader class 2308 $this.parents("li").removeClass("loading"); 2309 2310 listeo_bookings_manage(); 2311 }, 2312 }); 2313 }); 2314 $(document).on("click", ".mark-as-paid", function (e) { 2315 e.preventDefault(); 2316 var $this = $(this); 2317 $this.parents("li").addClass("loading"); 2318 var status = "paid"; 2319 2320 // preparing data for ajax 2321 var ajax_data = { 2322 action: "listeo_bookings_manage", 2323 booking_id: $(this).data("booking_id"), 2324 status: status, 2325 //'nonce': nonce 2326 }; 2327 $.ajax({ 2328 type: "POST", 2329 dataType: "json", 2330 url: listeo.ajaxurl, 2331 data: ajax_data, 2332 2333 success: function (data) { 2334 // display loader class 2335 $this.parents("li").removeClass("loading"); 2336 2337 listeo_bookings_manage(); 2338 }, 2339 }); 2340 }); 2341 2342 var start = moment().subtract(30, "days"); 2343 var end = moment(); 2344 2345 function cb(start, end) {
2346 $("#booking-date-range span,#chart-date-range span").html( 2347 start.format("MMMM D, YYYY") + " - " + end.format("MMMM D, YYYY") 2348 ); 2349 } 2350 2351 var ranges = new Object(); 2352 ranges[listeo_core.today] = [moment().subtract(1, "days"), moment()]; 2353 ranges[listeo_core.yesterday] = [ 2354 moment().subtract(1, "days"), 2355 moment().subtract(1, "days"), 2356 ]; 2357 ranges[listeo_core.last_7_days] = [moment().subtract(6, "days"), moment()]; 2358 ranges[listeo_core.last_30_days] = [ 2359 moment().subtract(29, "days"), 2360 moment(), 2361 ]; 2362 ranges[listeo_core.this_month] = [ 2363 moment().startOf("month"), 2364 moment().endOf("month"), 2365 ]; 2366 ranges[listeo_core.last_month] = [ 2367 moment().subtract(1, "month").startOf("month"), 2368 moment().subtract(1, "month").endOf("month"), 2369 ]; 2370 2371 var today = listeo_core.today; 2372 2373 $("#booking-date-range-enabler").on("click", function (e) { 2374 e.preventDefault(); 2375 $(this).hide(); 2376 cb(start, end); 2377 $("#booking-date-range,#chart-date-range") 2378 .show() 2379 .daterangepicker( 2380 { 2381 opens: "left", 2382 autoUpdateInput: false, 2383 alwaysShowCalendars: true, 2384 startDate: start, 2385 endDate: end, 2386 ranges: ranges, 2387 locale: { 2388 format: wordpress_date_format.date, 2389 firstDay: parseInt(wordpress_date_format.day), 2390 applyLabel: listeo_core.applyLabel, 2391 cancelLabel: listeo_core.cancelLabel, 2392 fromLabel: listeo_core.fromLabel, 2393 toLabel: listeo_core.toLabel, 2394 customRangeLabel: listeo_core.customRangeLabel, 2395 daysOfWeek: [ 2396 listeo_core.day_short_su, 2397 listeo_core.day_short_mo, 2398 listeo_core.day_short_tu, 2399 listeo_core.day_short_we, 2400 listeo_core.day_short_th, 2401 listeo_core.day_short_fr, 2402 listeo_core.day_short_sa, 2403 ], 2404 monthNames: [ 2405 listeo_core.january, 2406 listeo_core.february, 2407 listeo_core.march, 2408 listeo_core.april, 2409 listeo_core.may, 2410 listeo_core.june, 2411 listeo_core.july, 2412 listeo_core.august, 2413 listeo_core.september, 2414 listeo_core.october, 2415 listeo_core.november, 2416 listeo_core.december, 2417 ], 2418 }, 2419 }, 2420 cb 2421 ) 2422 .trigger("click"); 2423 cb(start, end); 2424 }); 2425 2426 // Calendar animation and visual settings 2427 $("#booking-date-range").on("show.daterangepicker", function (ev, picker) { 2428 $(".daterangepicker").addClass( 2429 "calendar-visible calendar-animated bordered-style" 2430 ); 2431 $(".daterangepicker").removeClass("calendar-hidden"); 2432 }); 2433 $("#booking-date-range").on("hide.daterangepicker", function (ev, picker) { 2434 $(".daterangepicker").removeClass("calendar-visible"); 2435 $(".daterangepicker").addClass("calendar-hidden"); 2436 }); 2437 } // end if dashboard booking 2438 2439 // $('a.reject').on('click', function() { 2440 2441 // console.log(picker); 2442 2443 // }); 2444 2445 /*----------------------------------------------------*/ 2446 /* Time Slots Carousel Functions 2447 /*----------------------------------------------------*/ 2448 if ($('.time-slots-carousel-container').length) { 2449 2450 var currentPosition = 0; 2451 var $track = $('.slot-carousel-track'); 2452 var $columns = $('.slot-carousel-day-column'); 2453 var columnWidth = 0; 2454 var visibleColumns = 3; 2455 var maxPosition = 0; 2456 2457 // Set Moment.js locale from WordPress 2458 if (typeof moment !== 'undefined' && typeof listeo_core !== 'undefined' && listeo_core.wp_locale) { 2459 moment.locale(listeo_core.wp_locale); 2460 } 2461 2462 // Initialize carousel on page load 2463 initializeCarousel(); 2464 2465 // Handle slot selection 2466 $('.time-slots-carousel-container').on('click', '.calendar-slot-available', function(e) { 2467 e.preventDefault(); 2468 2469 // Remove previous selection 2470 $('.calendar-slot').removeClass('selected'); 2471 2472 // Add selection to clicked slot 2473 $(this).addClass('selected'); 2474 2475 // Get slot data 2476 var slotValue = $(this).data('slot-value'); 2477 var slotTime = $(this).data('slot-time'); 2478 2479 // Get the date from the parent column 2480 var selectedDate = $(this).closest('.slot-carousel-day-column').data('date'); 2481 2482 // Update hidden slot input 2483 $('#slot').val(JSON.stringify([slotTime, slotValue])); 2484 2485 // Set the date picker value programmatically (required for check_booking to work) 2486 if ($('#date-picker').length && selectedDate) { 2487 var datePicker = $('#date-picker').data('daterangepicker'); 2488 if (datePicker && typeof moment !== 'undefined') { 2489 var momentDate = moment(selectedDate); 2490 datePicker.setStartDate(momentDate); 2491 datePicker.setEndDate(momentDate); 2492 } 2493 } 2494 2495 // Check booking availability 2496 if (typeof check_booking === 'function') { 2497 check_booking(); 2498 } 2499 }); 2500 2501 // Navigation buttons - move by visible columns count 2502 $('.slot-carousel-nav-btn.prev-day').on('click', function() { 2503 if (currentPosition > 0) { 2504 currentPosition = Math.max(0, currentPosition - visibleColumns); 2505 updateCarouselPosition(); 2506 } 2507 }); 2508
2509 $('.slot-carousel-nav-btn.next-day').on('click', function() { 2510 if (currentPosition < maxPosition) { 2511 currentPosition = Math.min(maxPosition, currentPosition + visibleColumns); 2512 updateCarouselPosition(); 2513 2514 // Check if we're within 7 days of the end, load more days if needed 2515 var totalColumns = $columns.length; 2516 var daysFromEnd = totalColumns - (currentPosition + visibleColumns); 2517 if (daysFromEnd <= 7) { 2518 appendMoreDays(14); 2519 } 2520 } 2521 }); 2522 2523 // Append more days to carousel dynamically using AJAX 2524 function appendMoreDays(daysToAdd) { 2525 if (typeof moment === 'undefined') return; 2526 2527 // Get the last loaded date from data attribute 2528 var $container = $('.time-slots-carousel-container'); 2529 var lastLoadedDate = $container.data('availability-loaded-until'); 2530 2531 if (!lastLoadedDate) { 2532 // Fallback to last column's date if attribute not found 2533 var $lastColumn = $('.slot-carousel-day-column').last(); 2534 if (!$lastColumn.length) return; 2535 lastLoadedDate = $lastColumn.data('date'); 2536 } 2537 2538 var startDate = moment(lastLoadedDate).add(1, 'days'); 2539 var endDate = moment(startDate).add(daysToAdd - 1, 'days'); 2540 2541 // Make AJAX request for availability 2542 $.ajax({ 2543 type: 'POST', 2544 url: listeo.ajaxurl, 2545 data: { 2546 action: 'get_carousel_slots_availability', 2547 listing_id: $('#listing_id').val(), 2548 date_start: startDate.format('YYYY-MM-DD'), 2549 date_end: endDate.format('YYYY-MM-DD') 2550 }, 2551 success: function(response) { 2552 if (response.success && response.data) { 2553 var availabilityData = response.data; 2554 2555 // Iterate through each day in the response 2556 for (var dateStr in availabilityData) { 2557 if (!availabilityData.hasOwnProperty(dateStr)) continue; 2558 2559 var currentDate = moment(dateStr); 2560 var dayOfWeek = (currentDate.isoWeekday() % 7); 2561 var daySlots = availabilityData[dateStr]; 2562 var hasSlots = Array.isArray(daySlots) && daySlots.length > 0; 2563 2564 // Get day name and date with translations 2565 var dayName = currentDate.format('ddd,'); 2566 2567 // Get translated month abbreviation 2568 var monthNames = [ 2569 listeo_core.month_abbrev_jan, listeo_core.month_abbrev_feb, listeo_core.month_abbrev_mar, 2570 listeo_core.month_abbrev_apr, listeo_core.month_abbrev_may, listeo_core.month_abbrev_jun, 2571 listeo_core.month_abbrev_jul, listeo_core.month_abbrev_aug, listeo_core.month_abbrev_sep, 2572 listeo_core.month_abbrev_oct, listeo_core.month_abbrev_nov, listeo_core.month_abbrev_dec 2573 ]; 2574 var monthIndex = currentDate.month(); 2575 var dateShort = currentDate.format('D') + ' ' + monthNames[monthIndex]; 2576 2577 // Build HTML for the new column 2578 var columnHtml = '<div class="slot-carousel-day-column" data-day="' + dayOfWeek + '" data-date="' + dateStr + '">'; 2579 columnHtml += '<div class="calendar-day ' + (hasSlots ? 'day-available' : 'day-placeholder') + '">'; 2580 columnHtml += '<div class="calendar-day-date">'; 2581 columnHtml += '<p class="day-name">' + dayName + '</p>'; 2582 columnHtml += '<p class="day-date">' + dateShort + '</p>'; 2583 columnHtml += '</div>'; 2584 columnHtml += '<div class="calendar-day-slots">'; 2585 columnHtml += '<ul class="calendar-day-slots-list">'; 2586 2587 if (hasSlots) { 2588 // Add actual available slots 2589 var maxVisible = 10; 2590 var totalSlots = daySlots.length; 2591 2592 for (var j = 0; j < totalSlots; j++) { 2593 var slot = daySlots[j]; 2594 var timeRange = slot.time; 2595 var available = slot.available; 2596 var slotKey = slot.slot_key; 2597 var slotDayOfWeek = slot.day_of_week; 2598 var isBooked = slot.is_booked || false; 2599 2600 var timeParts = timeRange.split('-'); 2601 var startTime = timeParts[0].trim(); 2602 2603 // Hide slots after the 10th 2604 var hiddenClass = (j >= maxVisible) ? ' hidden-slot' : ''; 2605 2606 // Determine slot class based on booked status 2607 var slotClass = isBooked ? 'calendar-slot calendar-slot-booked' : 'calendar-slot calendar-slot-available'; 2608 var slotDisabled = isBooked ? ' disabled' : ''; 2609 2610 columnHtml += '<li class="calendar-day-slots-list-item' + hiddenClass + '">'; 2611 columnHtml += '<button type="button" class="' + slotClass + '"'; 2612 columnHtml += ' data-slot-value="' + slotDayOfWeek + '|' + slotKey + '"'; 2613 columnHtml += ' data-slot-time="' + timeRange + '"' + slotDisabled + '>'; 2614 columnHtml += startTime; 2615 columnHtml += '</button>'; 2616 columnHtml += '</li>'; 2617 } 2618 2619 // Add "Show More" button if there are more than 10 slots 2620 if (totalSlots > maxVisible) { 2621 var moreCount = totalSlots - maxVisible; 2622 var showMoreText = (typeof listeo_core !== 'undefined' && listeo_core.show_more_slots) 2623 ? listeo_core.show_more_slots.replace('%d', more
2623Count) 2624 : 'Show ' + moreCount + ' more'; 2625 columnHtml += '<li class="calendar-day-slots-list-item show-more-item">'; 2626 columnHtml += '<button type="button" class="calendar-slot-show-more">'; 2627 columnHtml += showMoreText; 2628 columnHtml += '</button>'; 2629 columnHtml += '</li>'; 2630 } 2631 } else { 2632 // Add placeholder slots 2633 for (var p = 0; p < 6; p++) { 2634 columnHtml += '<li class="calendar-day-slots-list-item">'; 2635 columnHtml += '<button type="button" class="calendar-slot calendar-slot-placeholder" disabled tabindex="-1">-</button>'; 2636 columnHtml += '</li>'; 2637 } 2638 } 2639 2640 columnHtml += '</ul>'; 2641 columnHtml += '</div>'; 2642 columnHtml += '</div>'; 2643 columnHtml += '</div>'; 2644 2645 // Append to track 2646 $('.slot-carousel-track').append(columnHtml); 2647 } 2648 2649 // Update the last loaded date 2650 $container.data('availability-loaded-until', endDate.format('YYYY-MM-DD')); 2651 $container.attr('data-availability-loaded-until', endDate.format('YYYY-MM-DD')); 2652 2653 // Recalculate dimensions and height after new columns are fully rendered 2654 setTimeout(function() { 2655 $columns = $('.slot-carousel-day-column'); 2656 calculateDimensions(); 2657 updateCarouselHeight(); 2658 }, 100); 2659 } 2660 }, 2661 error: function(xhr, status, error) { 2662 console.error('Failed to load more carousel days:', error); 2663 } 2664 }); 2665 } 2666 2667 // Initialize carousel 2668 function initializeCarousel() { 2669 calculateDimensions(); 2670 updateCarouselPosition(); 2671 2672 // Recalculate height and show carousel 2673 setTimeout(function() { 2674 updateCarouselHeight(); 2675 2676 // Add loaded class - CSS removes max-height restriction and hides loader 2677 $('.time-slots-carousel-container').addClass('slot-carousel-loaded'); 2678 }, 100); 2679 2680 // Recalculate on window resize 2681 $(window).on('resize', debounce(function() { 2682 calculateDimensions(); 2683 updateCarouselPosition(); 2684 }, 250)); 2685 } 2686 2687 // Calculate carousel dimensions 2688 function calculateDimensions() { 2689 var viewportWidth = $('.slot-carousel-viewport').width(); 2690 2691 // Determine visible columns based on screen size 2692 if ($(window).width() <= 1360) { 2693 visibleColumns = 2; 2694 } else { 2695 visibleColumns = 3; 2696 } 2697 2698 // Get actual column width from rendered element (includes gap in flex layout) 2699 if ($columns.length > 0) { 2700 var firstColumn = $columns.first(); 2701 var columnActualWidth = firstColumn.outerWidth(); // Gets the actual rendered width 2702 var gap = 15; // Gap between columns 2703 columnWidth = columnActualWidth + gap; // Width + gap = distance to move 2704 } else { 2705 columnWidth = viewportWidth / visibleColumns; 2706 } 2707 2708 // Calculate max position 2709 var totalColumns = $columns.length; 2710 maxPosition = Math.max(0, totalColumns - visibleColumns); 2711 2712 // Reset position if needed 2713 if (currentPosition > maxPosition) { 2714 currentPosition = maxPosition; 2715 } 2716 } 2717 2718 // Update carousel position 2719 function updateCarouselPosition() { 2720 var translateX = -(currentPosition * columnWidth); 2721 $track.css('transform', 'translateX(' + translateX + 'px)'); 2722 2723 // Update navigation button states 2724 $('.slot-carousel-nav-btn.prev-day').prop('disabled', currentPosition === 0); 2725 $('.slot-carousel-nav-btn.next-day').prop('disabled', currentPosition >= maxPosition); 2726 2727 // Update carousel height based on visible columns 2728 updateCarouselHeight(); 2729 } 2730 2731 // Calculate and set carousel height based on tallest visible column 2732 function updateCarouselHeight() { 2733 var maxHeight = 0; 2734 2735 // Get visible column range 2736 var visibleStart = currentPosition; 2737 var visibleEnd = currentPosition + visibleColumns; 2738 2739 // Refresh columns reference to ensure we have the latest DOM state 2740 $columns = $('.slot-carousel-day-column'); 2741 2742 // Find tallest column in visible range (avoid variable name collision) 2743 var $visibleColumnElements = $columns.slice(visibleStart, visibleEnd); 2744 2745 if ($visibleColumnElements.length === 0) return; 2746 2747 $visibleColumnElements.each(function() { 2748 var $calendarDay = $(this).find('.calendar-day'); 2749 2750 if (!$calendarDay.length || !$calendarDay[0]) return; 2751 2752 // Force reflow to get accurate height 2753 var height = $calendarDay[0].offsetHeight; 2754 2755 // Get computed margins 2756 var marginTop = parseInt($(this).css('margin-top')) || 0; 2757 var marginBottom = parseInt($(this).css('margin-bottom')) || 0; 2758 var columnHeight = height + marginTop + marginBottom; 2759 2760 if (columnHeight > maxHeight) { 2761 maxHeight = columnHeight; 2762 } 2763 }); 2764 2765 // Account for viewport padding 2766 var $viewport = $('.slot-carousel-viewport'); 2767 var viewportPaddingTop = parseInt($viewport.css('padding-top')) || 0; 2768 var viewportPaddingBottom = parseInt($viewport.css('padding-bottom')) || 0; 2769 var totalPadding = viewportPaddingTop + viewportPaddingBottom; 2770 2771 // Set viewport height (content height + viewport padding) 2772 if (maxHeight > 0) { 2773 $viewport.css('height', (maxHeight + totalPadding) + 'px'); 2774 } 2775 } 2776 2777 // Debounce function for resize 2778 function debounce(func, wait) { 2779 var timeout; 2780 return function() { 2781 var context = this, args = arguments; 2782 clearTimeout(timeout); 2783 timeout = setTimeout(function() {
2784 func.apply(context, args); 2785 }, wait); 2786 }; 2787 } 2788 2789 // Handle "Show More" button clicks 2790 $('.time-slots-carousel-container').on('click', '.calendar-slot-show-more', function(e) { 2791 e.preventDefault(); 2792 2793 var $calendarDay = $(this).closest('.calendar-day'); 2794 $calendarDay.addClass('expanded'); 2795 2796 // Recalculate height after expansion 2797 setTimeout(function() { 2798 updateCarouselHeight(); 2799 }, 50); 2800 }); 2801 2802 // Sync with date picker if it exists 2803 $('#date-picker').on('apply.daterangepicker', function(ev, picker) { 2804 if ($('.time-slots-carousel-container').length) { 2805 var selectedDate = picker.startDate; 2806 var targetDateStr = selectedDate.format('YYYY-MM-DD'); 2807 2808 // Find the matching day column 2809 var $targetColumn = $('.carousel-day-column[data-date="' + targetDateStr + '"]'); 2810 2811 if ($targetColumn.length) { 2812 var targetIndex = $targetColumn.index(); 2813 2814 // Calculate position to show this column 2815 currentPosition = Math.max(0, Math.min(targetIndex, maxPosition)); 2816 updateCarouselPosition(); 2817 } 2818 } 2819 }); 2820 } 2821 2822}); 2823 2824})(this.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.