jQuery(document).ready(function ($){
function detachElementAndPosition(elementId, parentId){
let $element=$(`#${elementId}`);
let $parent=$(`#${parentId}`);
$element.hide().appendTo('body');
$parent.on('click', function (){
let offset=$(this).offset();
$element.css({
top: offset.top + $(this).outerHeight(),
left: offset.left
}).show();
});
if($('.ph_controls').length===0){
$('#ph_book_search_number_of_participants_container').hide();
}}
detachElementAndPosition('ph_book_search_asset_list', 'ph_book_search_asset_name_container');
detachElementAndPosition('ph_book_search_number_of_participants_button', 'ph_book_search_number_of_participants_container');
let date_format=$("#ph_book_search_date_format").val();
let fixedDate=$('#ph_fixed_date').val()==='1';
let timeEnabled=$('#ph_book_search_filter_date_and_time').val()==='1';
let timeInterval=parseInt($('#ph_book_search_interval').val())||5;
let assetLabel=$('#ph_asset_label').val();
let borderRadius=$('#ph_border_radius').val();
let borderStyle=$('#ph_border_style').val();
let borderColor=$('#ph_border_color').val();
let borderWidth=$('#ph_border_width').val();
let daily_min_time=$('#ph_book_search_daily_range_from').val()||"00:00";
let daily_max_time=$('#ph_book_search_daily_range_to').val()||"23:59";
let time_format=$('#ph_book_search_filter_time_format').val();
let time_format_txt=time_format==='time_24hr' ? ' H:i':' h:i K';
let momentTimeFormat=time_format==='time_24hr' ? ' HH:mm':' hh:mm A';
let availableCheckInDays=$("#ph_checkin_day_related").val() ? $("#ph_checkin_day_related").val().split(' ').filter(Boolean):["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
let availableCheckOutDays=$("#ph_checkout_day_related").val() ? $("#ph_checkout_day_related").val().split(' ').filter(Boolean):["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
let dateFormatOptions=[
'd-m-Y',
'Y-m-d',
'd/m/Y',
'Y/m/d',
'd.m.Y',
'D, d M Y',
'M j, Y',
'F j, Y',
'M j Y'
];
if($.inArray(date_format, dateFormatOptions)===-1){
date_format='d-m-Y';
}
let getUrlParameter=(sParam)=> {
let sPageURL=window.location.search.substring(1);
let sURLVariables=sPageURL.split('&');
for (let i=0; i < sURLVariables.length; i++){
let sParameterName=sURLVariables[i].split('=');
if(sParameterName[0]===sParam){
return sParameterName[1] ? decodeURIComponent(sParameterName[1].replace(/\+/g, ' ')):true;
}}
return null;
};
function convertToMomentFormat(flat){
const map={
'd-m-Y': 'DD-MM-YYYY',
'Y-m-d': 'YYYY-MM-DD',
'd/m/Y': 'DD/MM/YYYY',
'Y/m/d': 'YYYY/MM/DD',
'd.m.Y': 'DD.MM.YYYY',
'D, d M Y': 'ddd, DD MMM YYYY',
'M j, Y': 'MMM D, YYYY',
'F j, Y': 'MMMM D, YYYY',
'M j Y': 'MMM D YYYY'
};
return map[flat]||'DD-MM-YYYY';
}
function getInitialSelectedDate(minTimeStr){
let resultFrom=getUrlParameter('book_search_from');
if(resultFrom){
$("#ph_book_search_from").val(resultFrom);
const momentFormat=convertToMomentFormat(date_format) + (timeEnabled ? momentTimeFormat:'');
let initialDate=moment(resultFrom, momentFormat).toDate();
if(isNaN(initialDate.getTime())){
$("#ph_book_search_from").val('');
return null;
}
return initialDate;
}else{
return null;
}}
let initialFromDate=getInitialSelectedDate(daily_min_time);
let resultTo=getUrlParameter('book_search_to');
let initialToDate=null;
if(resultTo){
$("#ph_book_search_to").val(resultTo);
const momentFormat=convertToMomentFormat(date_format) + (timeEnabled ? momentTimeFormat:'');
let parsedDate=moment(resultTo, momentFormat).toDate();
if(!isNaN(parsedDate.getTime())){
initialToDate=parsedDate;
}else{
$("#ph_book_search_to").val('');
}}
let flatpickrDateFormat=date_format;
let flatpickrDateTimeFormat=date_format + time_format_txt;
function makeDisableFn(availableDays, minDateForTo){
return function (date){
let incomingDay=flatpickr.formatDate(date, "l");
let isRestrictedDay = !availableDays.includes(incomingDay);
return isRestrictedDay;
};}
function updateCustomYearSelect(instance, targetYear){
let yearSelect=instance.calendarContainer.querySelector(".flatpickr-year-select");
if(yearSelect){
yearSelect.value=targetYear;
instance.currentYear=targetYear;
let yearInput=instance.calendarContainer.querySelector(".cur-year");
if(yearInput) yearInput.value=targetYear;
}}
function createTimeControls(instance){
setTimeout(function (){
if(!instance||!instance.calendarContainer) return;
let yearInput=instance.calendarContainer.querySelector(".cur-year");
if(yearInput&&yearInput.parentNode){
let yearWrapper=yearInput.parentNode;
let existingSelect=instance.calendarContainer.querySelector(".flatpickr-year-select");
if(existingSelect) existingSelect.remove();
let yearSelect=document.createElement("select");
yearSelect.className="flatpickr-year-select";
let minValidYear=instance.config.minDate&&instance.config.minDate instanceof Date
? instance.config.minDate.getFullYear()
: new Date().getFullYear();
let cy=new Date().getFullYear();
const startYear=Math.max(cy, minValidYear);
let currentYearForSelection=instance.currentYear < startYear
? startYear
: instance.currentYear;
for (let y=startYear; y <=cy + 2; y++){
let opt=document.createElement("option");
opt.value=y;
opt.textContent=y;
if(y===currentYearForSelection) opt.selected=true;
yearSelect.appendChild(opt);
}
yearWrapper.parentNode.replaceChild(yearSelect, yearWrapper);
yearSelect.addEventListener('change', function (){
instance.changeYear(parseInt(this.value, 10));
});
}}, 0);
}
function generateTimeOptions(instance){
let options=[];
let time24hr=instance.config.time_24hr;
let timeMomentFormat=time_format==='time_24hr' ? 'HH:mm':'hh:mm A';
let minTime=moment(daily_min_time, timeMomentFormat);
let maxTime=moment(daily_max_time, timeMomentFormat);
let currentTime=moment().hour(minTime.hour()).minute(minTime.minute()).second(0).millisecond(0);
if(!minTime.isValid()){
currentTime=moment().hour(0).minute(0).second(0).millisecond(0);
minTime=currentTime.clone();
}
while (currentTime.isSameOrBefore(maxTime)){
let h=currentTime.hour();
let m=currentTime.minute();
let displayHour=h;
let ampm='';
let valueHour=h.toString().padStart(2, '0');
let valueMinute=m.toString().padStart(2, '0');
if(!time24hr){
ampm=h < 12 ? ' AM':' PM';
displayHour=(h % 12)||12;
}
let displayTime=`${displayHour.toString().padStart(2, '0')}:${valueMinute}${ampm}`;
let valueTime=`${valueHour}:${valueMinute}`;
options.push({ value: valueTime, display: displayTime });
currentTime.add(timeInterval, 'minutes');
}
return options;
}
function sameDay(a, b){
return a&&b &&
a.getFullYear()===b.getFullYear() &&
a.getMonth()===b.getMonth() &&
a.getDate()===b.getDate();
}
function createTimeDropdowns(instance){
if(!instance||!instance.calendarContainer||!timeEnabled) return;
const timeContainer=instance.calendarContainer.querySelector(".flatpickr-time");
if(!timeContainer) return;
if(timeContainer.querySelector(".ph-hour-select")) return;
timeContainer.innerHTML='';
const timeOptions=generateTimeOptions(instance);
if(!timeOptions||!timeOptions.length){
const info=document.createElement('div');
info.textContent='No available times';
timeContainer.appendChild(info);
return;
}
const valueFormat='HH:mm';
let allowedMin=moment(daily_min_time, valueFormat);
let allowedMax=moment(daily_max_time, valueFormat);
const configMinDate=instance.config&&instance.config.minDate instanceof Date ? moment(instance.config.minDate):null;
const selectedDateObj=instance.selectedDates[0]||null;
const now=moment();
if(selectedDateObj){
const selectedIsToday=sameDay(selectedDateObj, new Date());
const selectedIsConfigMin=configMinDate&&sameDay(selectedDateObj, configMinDate.toDate());
if(selectedIsToday){
if(now.isAfter(allowedMin)){
const minutes=now.minute();
const rem=minutes % timeInterval;
const roundedMinutes=rem===0 ? minutes:minutes + (timeInterval - rem);
const roundedNow=now.clone().minute(roundedMinutes).second(0).millisecond(0);
if(roundedNow.isAfter(allowedMin)) allowedMin=roundedNow;
}}
if(configMinDate&&sameDay(selectedDateObj, configMinDate.toDate())){
if(configMinDate.isAfter(allowedMin)) allowedMin=configMinDate.clone();
}}
const filtered=timeOptions.filter(opt=> {
const [hh, mm]=opt.value.split(':').map(Number);
const optMoment=(selectedDateObj ? moment(selectedDateObj):moment()).hour(hh).minute(mm).second(0).millisecond(0);
let compareMin=allowedMin.clone().year(optMoment.year()).month(optMoment.month()).date(optMoment.date());
let compareMax=allowedMax.clone().year(optMoment.year()).month(optMoment.month()).date(optMoment.date());
return optMoment.isSameOrAfter(compareMin)&&optMoment.isSameOrBefore(compareMax);
});
if(!filtered.length){
const info=document.createElement('div');
info.textContent='No available times';
timeContainer.appendChild(info);
return;
}
const hourMap={};
filtered.forEach(opt=> {
const [hh, mm]=opt.value.split(':').map(Number);
if(!hourMap[hh]) hourMap[hh]=[];
if(hourMap[hh].indexOf(mm)===-1) hourMap[hh].push(mm);
});
const hours=Object.keys(hourMap).map(Number).sort((a, b)=> a - b);
const hourSelect=document.createElement('select');
hourSelect.className='ph-hour-select';
hours.forEach(h=> {
const opt=document.createElement('option');
opt.value=h;
if(instance.config.time_24hr){
opt.textContent=String(h).padStart(2, '0');
}else{
const displayHour=(h % 12)||12;
const ampm=h < 12 ? ' AM':' PM';
opt.textContent=displayHour.toString() + ampm;
}
hourSelect.appendChild(opt);
});
let initialHour=selectedDateObj ? selectedDateObj.getHours():hours[0];
if(hours.indexOf(initialHour)===-1){
const next=hours.find(h=> h >=initialHour);
initialHour=typeof next!=='undefined' ? next:hours[0];
}
hourSelect.value=initialHour;
const minuteSelect=document.createElement('select');
minuteSelect.className='ph-minute-select';
function populateMinutesForHour(h){
minuteSelect.innerHTML='';
const minutes=(hourMap[h]||[]).slice().sort((a, b)=> a - b);
if(minutes.length===0) return;
minutes.forEach(m=> {
const opt=document.createElement('option');
opt.value=m;
opt.textContent=String(m).padStart(2, '0');
minuteSelect.appendChild(opt);
});
let initMinute=selectedDateObj&&selectedDateObj.getHours()===h ? selectedDateObj.getMinutes():minutes[0];
if(minutes.indexOf(initMinute)===-1){
const nextM=minutes.find(mm=> mm >=initMinute);
initMinute=typeof nextM!=='undefined' ? nextM:minutes[0];
}
if(minutes.length) minuteSelect.value=initMinute;
}
populateMinutesForHour(Number(hourSelect.value));
const wrapper=document.createElement('div');
wrapper.className='ph-time-select-wrapper';
wrapper.style.display='flex';
wrapper.style.gap='6px';
wrapper.style.justifyContent='center';
wrapper.appendChild(hourSelect);
wrapper.appendChild(document.createTextNode(':'));
wrapper.appendChild(minuteSelect);
timeContainer.appendChild(wrapper);
hourSelect.addEventListener('change', function (){
const h=Number(this.value);
populateMinutesForHour(h);
const m=minuteSelect.value ? Number(minuteSelect.value):0;
const newDate=instance.selectedDates[0] ? new Date(instance.selectedDates[0]):new Date();
newDate.setHours(h, m, 0, 0);
instance.setDate(newDate, false);
});
minuteSelect.addEventListener('change', function (){
const h=Number(hourSelect.value);
const m=Number(this.value);
const newDate=instance.selectedDates[0] ? new Date(instance.selectedDates[0]):new Date();
newDate.setHours(h, m, 0, 0);
instance.setDate(newDate, false);
});
}
let baseFlatpickrConfig={
enableTime: timeEnabled,
dateFormat: timeEnabled ? flatpickrDateTimeFormat:flatpickrDateFormat,
time_24hr: time_format==='time_24hr',
minDate: "today",
weekNumbers: false,
closeOnSelect: false,
onReady: function (selectedDates, dateStr, instance){
createTimeControls(instance);
createTimeDropdowns(instance);
if(!instance.calendarContainer.querySelector('.ph_flatpickr_apply_button')){
let btnWrap=document.createElement('div');
btnWrap.style.textAlign='center';
btnWrap.style.padding='10px';
let applyBtn=document.createElement('button');
applyBtn.className='ph_flatpickr_apply_button';
applyBtn.textContent='Apply';
applyBtn.addEventListener('click', function (){
if(instance.selectedDates.length){
let sel=instance.selectedDates[0];
instance.close();
let formatted=moment(sel).format(convertToMomentFormat(date_format) + (timeEnabled ? momentTimeFormat:''));
$(instance.input).val(formatted);
if(instance===fromFlatpickr&&!fixedDate&&toFlatpickr){
toFlatpickr.set('minDate', sel);
toFlatpickr.set('minDate', moment(sel).subtract(1, 'days').toDate());
toFlatpickr.set('minDate', sel);
let fn=makeDisableFn(availableCheckOutDays, sel);
toFlatpickr.set('disable', [fn]);
createTimeControls(toFlatpickr);
try { toFlatpickr.redraw(); } catch (e){}
let currentToDate=toFlatpickr.selectedDates[0];
if(currentToDate&&currentToDate.getTime() < sel.getTime()){
toFlatpickr.clear();
$('#ph_book_search_to').val(formatted);
toFlatpickr.setDate(sel, true);
}
toFlatpickr.setDate(toFlatpickr.selectedDates[0]||sel, true);
updateCustomYearSelect(toFlatpickr, toFlatpickr.selectedDates[0] ? toFlatpickr.selectedDates[0].getFullYear():sel.getFullYear());
toFlatpickr.changeYear(toFlatpickr.selectedDates[0] ? toFlatpickr.selectedDates[0].getFullYear():sel.getFullYear());
toFlatpickr.open();
}}
});
btnWrap.appendChild(applyBtn);
instance.calendarContainer.appendChild(btnWrap);
}},
onChange: function(selectedDates, dateStr, instance){
if(timeEnabled&&selectedDates.length > 0){
let sel=selectedDates[0];
let valueFormat='HH:mm';
let minTimeMoment=moment(daily_min_time, valueFormat);
let selectedMoment=moment(sel);
let selTimeOnly=selectedMoment.clone().year(2000).month(0).date(1);
let minTimeOnly=minTimeMoment.clone().year(2000).month(0).date(1);
if(selTimeOnly.isBefore(minTimeOnly)){
let newTimeCandidate=selectedMoment.clone()
.hour(minTimeMoment.hour())
.minute(minTimeMoment.minute());
let effectiveMinMoment=instance.config.minDate instanceof Date
? moment(instance.config.minDate)
: moment().startOf('day');
if(newTimeCandidate.isBefore(effectiveMinMoment)){
newTimeCandidate=effectiveMinMoment;
}
sel=newTimeCandidate.toDate();
instance.setDate(sel, false);
let formatted=moment(sel).format(convertToMomentFormat(date_format) + momentTimeFormat);
$(instance.input).val(formatted);
}
let timeContainer=instance.calendarContainer.querySelector(".flatpickr-time");
if(timeContainer) timeContainer.innerHTML='';
createTimeDropdowns(instance);
}},
disable: [ makeDisableFn(availableCheckInDays, null) ]
};
let fromFlatpickr, toFlatpickr;
if(fixedDate){
$('.ph_book_search_date_container1').hide();
fromFlatpickr=flatpickr("#ph_book_search_from", {
...baseFlatpickrConfig,
defaultDate: initialFromDate,
disable: [ makeDisableFn(availableCheckInDays, null) ]
});
}else{
fromFlatpickr=flatpickr("#ph_book_search_from", {
...baseFlatpickrConfig,
mode: "single",
defaultDate: initialFromDate,
disable: [ makeDisableFn(availableCheckInDays, null) ]
});
let toConfig={
...baseFlatpickrConfig,
mode: "single",
minDate: initialFromDate||"today",
defaultDate: initialToDate,
disable: [ makeDisableFn(availableCheckOutDays, initialFromDate||null) ]
};
toFlatpickr=flatpickr("#ph_book_search_to", toConfig);
}
function syncDayCheckboxesToHidden(){
let checkedIn=$('.ph_checkin_day_checkbox:checked').map(function (){ return $(this).val(); }).get();
$('#ph_checkin_day_related').val(checkedIn.join(' '));
availableCheckInDays=checkedIn.length ? checkedIn:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
let checkedOut=$('.ph_checkout_day_checkbox:checked').map(function (){ return $(this).val(); }).get();
$('#ph_checkout_day_related').val(checkedOut.join(' '));
availableCheckOutDays=checkedOut.length ? checkedOut:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
if(fromFlatpickr){
fromFlatpickr.set('disable', [makeDisableFn(availableCheckInDays, null)]);
try { fromFlatpickr.redraw(); } catch (e){ }}
if(toFlatpickr){
let currentMin=toFlatpickr.config.minDate||initialFromDate||"today";
toFlatpickr.set('disable', [makeDisableFn(availableCheckOutDays, currentMin)]);
try { toFlatpickr.redraw(); } catch (e){ }}
}
$('.ph_checkin_day_checkbox, .ph_checkout_day_checkbox').on('change', syncDayCheckboxesToHidden);
let participantCounts=[];
let updateTotalParticipants=()=> {
let total=$('.ph_participant-group input[type="number"]').get().reduce((sum, el)=> sum + (parseInt(el.value)||0), 0);
$('#participant_count_display').text(total > 0 ? total:'');
};
$("#ph_booking_clear").click(function (){
$('#ph_book_search_from').val('').attr('placeholder', $('#ph_from_date_text').val()||'From');
$('#ph_book_search_to').val('').attr('placeholder', $('#ph_to_date_text').val()||'To');
$('#ph_asset_name').val(assetLabel).attr('data-ph-search-asset-id', 'default');
$("#participant_count_display").text('');
$('.ph_participant-group input[type="number"]').val(0);
participantCounts=[];
if(fromFlatpickr) fromFlatpickr.clear();
if(toFlatpickr){
toFlatpickr.clear();
toFlatpickr.set('minDate', "today");
}});
$('.ph-booking-participant-minus, .ph-booking-participant-plus').on('click', function (event){
event.stopPropagation();
let $input=$(this).siblings('input[type="number"]');
let value=parseInt($input.val())||0;
if($(this).hasClass('minus')){
value=Math.max(parseInt($input.attr('min')||0), value - 1);
}else{
value=Math.min(parseInt($input.attr('max')||999), value + 1);
}
$input.val(value);
updateTotalParticipants();
});
$('.ph_book_search_asset_item').on('click', function (){
let selectedText=$(this).text();
let assetId=$(this).attr('data-value');
$('#ph_asset_name').val(selectedText).attr('data-ph-search-asset-id', assetId);
$('#ph_book_search_asset_list').hide();
});
$('#ph_booking_searchform').on('submit', function (e){
let assetId=$('#ph_asset_name').attr('data-ph-search-asset-id');
$('#ph_search_asset_name').val(assetId==='default' ? '':assetId);
participantCounts=$('.ph_participant-group').map(function (){
let rule=$(this).data('participant').replace(/\+/g, ' ');
let count=parseInt($(this).find('input[type="number"]').val(), 10)||0;
return { rule, count };}).get();
$('#ph_book_search_participants').val(JSON.stringify(participantCounts));
});
updateTotalParticipants();
$('.ph_search_book_now').on('click', function (e){
e.preventDefault();
const data={
action: 'ph_handle_book_now_from_search',
product_id: $(this).attr('data-product-id')
};
$.post(ph_booking_search_data.ajaxurl, data, function (response){
if(response.success&&response?.data?.cart_url){
window.location.href=response.data.cart_url;
}});
});
});