PageSourceSearch

https://www.santuarioincoronatamontoro.it/wp-content/plugins/bookn…/assets/js/booknetic.js?v=1.3.5&ver=1.3.5

js santuarioincoronatamontoro.it collected 2026-09-28 03:22:49 UTC 79,711 bytes, 2,630 lines download raw bytes

1var bookneticPaymentStatus;
2
3(function($)
4{
5	"use strict";
6
7	if( typeof window.bookneticInitBookingPage !== 'undefined' )
8		return;
9
10	window.bookneticHooks = {
11
12		hooks: {},
13
14		addFilter: function ( key, fn, fn_id )
15		{
16			key = key.toLowerCase();
17
18			if ( ! this.hooks.hasOwnProperty( key ) )
19			{
20				this.hooks[ key ] = {};
21			}
22
23			if (fn === null && this.hooks[key].hasOwnProperty(fn_id)) {
24				delete this.hooks[key][fn_id];
25				return 0;
26			}
27
28			if (fn_id === undefined || fn_id === null) {
29				while(true) {
30					fn_id = Math.random().toString(36).substring(2, 15);
31					if (!this.hooks[key].hasOwnProperty(fn_id))
32						break;
33				}
34			}
35
36			this.hooks[ key ][ fn_id ] = fn;
37			return fn_id;
38		},
39
40		doFilter: function ( key, params, ...extra )
41		{
42			key = key.toLowerCase();
43
44			if (!this.hooks.hasOwnProperty( key ) ) {
45				return params;
46			}
47
48			for (let fn_id in this.hooks[key]) {
49				let fn = this.hooks[key][fn_id];
50
51				if ( typeof params === 'undefined' ) {
52					params = fn( ...extra );
53				} else {
54					params = fn( params, ...extra );
55				}
56			}
57
58			return params;
59		},
60
61		addAction: function ( key, fn, fn_id )
62		{
63			return this.addFilter( key, fn, fn_id );
64		},
65
66		doAction: function ( key, ...params )
67		{
68			this.doFilter( key, undefined, ...params );
69		}
70	};
71
72	function __( key )
73	{
74		return key in BookneticData.localization ? BookneticData.localization[ key ] : key;
75	}
76
77	$.fn.handleScrollBooknetic = function()
78	{
79		if ( !this.hasClass('nice-scrollbar-primary') && ! window.matchMedia('(max-width: 1000px)').matches )
80		{
81			this.addClass( 'nice-scrollbar-primary' );
82		}
83
84		if( window.matchMedia('(max-width: 1000px)').matches && this.hasClass('nice-scrollbar-primary') )
85		{
86			booking_panel_js.find(".booknetic_appointment_container_body").removeClass('nice-scrollbar-primary')
87
88			if ( $( '#country-listbox' ).length )
89			{
90				$( '#country-listbox' ).removeClass('nice-scrollbar-primary')
91			}
92
93			// return;
94		}
95	}
96
97	let index = 0;
98	window.bookneticInitBookingPage = function ( booking_panel_JS_element )
99	{
100		index++;
101		let booking_panel_js = $(booking_panel_JS_element);
102
103		if( booking_panel_js.data('booknetic_has_been_initiated') === true )
104			return;
105
106		booking_panel_js.data('booknetic_has_been_initiated', true);
107
108		let booknetic = {
109
110			google_recaptcha_token: null,
111			google_recaptcha_action: 'booknetic_booking_panel_' + index,
112
113			cartArr : [],
114			cartHTMLBody : [],
115			cartHTMLSideBar : [],
116			cartCurrentIndex:0,
117			cartErrors : {
118				a:[],
119				callbacks: [(arr)=>{
120					if( arr.length > 0 )
121					{
122						let itemIds = [];
123
124						arr.forEach((value)=>{
125							if( itemIds.indexOf(value['cart_item']) === -1)
126								itemIds.push(value['cart_item']);
127						});
128
129
130						booking_panel_js.find('.booknetic-cart-item-error .booknetic-cart-item-error-body').remove();
131						booking_panel_js.find('.booknetic-cart-item-error').removeClass('show');
132
133						arr.forEach((value)=>{
134							if(value['cart_item']!==undefined)
135							{
136								booking_panel_js.find('div.booknetic-cart div[data-index='+ value['cart_item'] +'] .booknetic-cart-item-error').addClass('show');
137								booking_panel_js.find('div.booknetic-cart div[data-index='+ value['cart_item'] +'] .booknetic-cart-item-error').append(`
138										<div class="booknetic-cart-item-error-body">${value['message']}</div>
139									`);
140							}
141						})
142
143
144					}
145					else
146					{
147						booking_panel_js.find('.booknetic-cart-item-error .booknetic-cart-item-error-body').remove();
148						booking_panel_js.find('.booknetic-cart-item-error').removeClass('show');
149					}
150				}],
151				get error()
152				{
153					return this.a;
154				},
155				set error(arr)
156				{
157					this.a = arr;
158					for (let i = 0; i < this.callbacks.length; i++) {
159						this.callbacks[i](arr );
160					}
161				}
162			},
163			__,
164
165			panel_js : booking_panel_js,
166
167			options: {
168				'templates': {
169					'loader': '<div class="booknetic_loading_layout"></div>'
170				}
171			},
172
173			localization: {
174				month_names: [ __('January'), __('February'), __('March'), __('April'), __('May'), __('June'), __('July'), __('August'), __('September'), __('October'), __('November'), __('December') ],
175				day_of_week: [ __('Sun'), __('Mon'), __('Tue'), __('Wed'), __('Thu'), __('Fri'), __('Sat'), __('Sun') ] ,
176			},
177
178			calendarDateTimes: {},
179			time_show_format: 1,
180			calendarYear: null,
181			calendarMonth: null,
182
183			paymentWindow: null,
184			paymentStatus: null,
185			appointmentId: null, // doit: bu failed payment olan appointmenti silmek ucundu, bunu payment_id ederik
186			ajaxResultConfirmStep: null,
187			paymentId: null,
188			dateBasedService: false,
189			serviceData: null,
190
191			globalDayOffs: {},
192			globalTimesheet: {},
193
194
195			loading: function ( onOff )
196			{
197				if( typeof onOff === 'undefined' || onOff )
198				{
199					booking_panel_js.find('#booknetic_progress').removeClass('booknetic_progress_done').show();
200					$({property: 0}).animate({property: 100}, {
201						duration: 1000,
202						step: function()
203						{
204							var _percent = Math.round(this.property);
205							if( !booking_panel_js.find('#booknetic_progress').hasClass('booknetic_progress_done') )
206							{
207								booking_panel_js.find('#booknetic_progress').css('width',  _percent+"%");
208							}
209						}
210					});
211
212					booking_panel_js.append( this.options.templates.loader );
213				}
214				else if( ! booking_panel_js.find('#booknetic_progress').hasClass('booknetic_progress_done') )
215				{
216					booking_panel_js.find('#booknetic_progress').addClass('booknetic_progress_done').css('width', 0);
217
218					// IOS bug...
219					setTimeout(function ()
220					{
221						booking_panel_js.find('.booknetic_loading_layout').remove();
222					}, 0);
223				}
224			},
225
226			htmlspecialchars_decode: function (string, quote_style)
227			{
228				var optTemp = 0,
229					i = 0,
230					noquotes = false;
231				if(typeof quote_style==='undefined')
232				{
233					quote_style = 2;
234				}
235				string = string.toString().replace(/&lt;/g, '<').replace(/&gt;/g, '>');
236				var OPTS = {
237					'ENT_NOQUOTES': 0,
238					'ENT_HTML_QUOTE_SINGLE': 1,
239					'ENT_HTML_QUOTE_DOUBLE': 2,
240					'ENT_COMPAT': 2,
241					'ENT_QUOTES': 3,
242					'ENT_IGNORE': 4
243				};
244				if(quote_style===0)
245				{
246					noquotes = true;
247				}
248				if(typeof quote_style !== 'number')
249				{
250					quote_style = [].concat(quote_style);
251					for (i = 0; i < quote_style.length; i++){
252						if(OPTS[quote_style[i]]===0){
253							noquotes = true;
254						} else if(OPTS[quote_style[i]]){
255							optTemp = optTemp | OPTS[quote_style[i]];
256						}
257					}
258					quote_style = optTemp;
259				}
260				if(quote_style & OPTS.ENT_HTML_QUOTE_SINGLE)
261				{
262					string = string.replace(/&#0*39;/g, "'");
263				}
264				if(!noquotes){
265					string = string.replace(/&quot;/g, '"');
266				}
267				string = string.replace(/&amp;/g, '&');
268				return string;
269			},
270
271			htmlspecialchars: function ( string, quote_style, charset, double_encode )
272			{
273				var optTemp = 0,
274					i = 0,
275					noquotes = false;
276				if(typeof quote_style==='undefined' || quote_style===null)
277				{
278					quote_style = 2;
279				}
280				string = typeof string != 'string' ? '' : string;
281
282				string = string.toString();
283				if(double_encode !== false){
284					string = string.replace(/&/g, '&amp;');
285				}
286				string = string.replace(/</g, '&lt;').replace(/>/g, '&gt;');
287				var OPTS = {
288					'ENT_NOQUOTES': 0,
289					'ENT_HTML_QUOTE_SINGLE': 1,
290					'ENT_HTML_QUOTE_DOUBLE': 2,
291					'ENT_COMPAT': 2,
292					'ENT_QUOTES': 3,
293					'ENT_IGNORE': 4
294				};
295				if(quote_style===0)
296				{
297					noquotes = true;
298				}
299				if(typeof quote_style !== 'number')
300				{
301					quote_style = [].concat(quote_style);
302					for (i = 0; i < quote_style.length; i++)
303					{
304						if(OPTS[quote_style[i]]===0)
305						{
306							noquotes = true;
307						}
308						else if(OPTS[quote_style[i]])
309						{
310							optTemp = optTemp | OPTS[quote_style[i]];
311						}
312					}
313					quote_style = optTemp;
314				}
315				if(quote_style & OPTS.ENT_HTML_QUOTE_SINGLE)
316				{
317					string = string.replace(/'/g, '&#039;');
318				}
319				if(!noquotes)
320				{
321					string = string.replace(/"/g, '&quot;');
322				}
323				return string;
324			},
325
326			sanitizeHTML: function (node){
327				node = node.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, '$1');
328				node = node.replace(/&lt;script\b[^&gt;]*&gt;([\s\S]*?)&lt;\/script&gt;/gi, '$1');
329				return node;
330			},
331
332			throttle: function(func, wait = 500) {
333				let processing = false;
334
335				return function (e) {
336					if (processing) return;
337
338					processing = true;
339
340					func.call(this, e);
341
342					setTimeout(() => {
343						processing = false;
344					}, wait);
345				}
346			},
347
348			getCurrentCartItem: () => {
349				return booknetic.cartArr[booknetic.cartCurrentIndex]
350			},
351
352			debounce: function(func, wait) {
353				let timeout;
354
355				return function(...args) {
356					clearTimeout(timeout);
357					timeout = setTimeout(() => func.apply(this, args), wait);
358				};
359			},
360
361			formDataToObject: function ( formData )
362			{
363				var object = {};
364
365				formData.forEach(function(value, key)
366				{
367					object[key] = value;
368				});
369
370				return object;
371			},
372
373			ajaxResultCheck: function ( res )
374			{
375
376				if( typeof res != 'object' )
377				{
378					try
379					{
380						res = JSON.parse(res);
381					}
382					catch(e)
383					{
384						this.toast( 'Error!' );
385						return false;
386					}
387				}
388
389				if( typeof res['status'] == 'undefined' )
390				{
391					this.toast( 'Error!' );
392					return false;
393				}
394
395				if( res['status'] === 'error' )
396				{
397					if( typeof res['errors'] != 'undefined' && res['errors'].length > 0)
398					{
399						return false;
400					}
401					this.toast( typeof res['error_msg'] == 'undefined' ? 'Error!' : res['error_msg'] );
402					return false;
403				}
404
405				if( res['status'] === 'ok' )
406					return true;
407
408				// else
409
410				this.toast( 'Error!' );
411				return false;
412			},
413
414			refreshNonce: function ( callback )
415			{
416				callback = typeof callback === 'function' ? callback : function(){};
417
418				if ( ! window.bookneticNonceRefreshingPromise )
419				{
420					window.bookneticNonceRefreshingPromise = new Promise( function ( resolve )
421					{
422						$.ajax( {
423							url:    BookneticData.ajax_url,
424							method: 'POST',
425							data:   { action: 'bkntc_ping', tenant_id: BookneticData.tenant_id }
426						} ).done( function ( rawResult )
427						{
428							let result;
429							try { result = JSON.parse( rawResult ); } catch ( e ) { result = rawResult; }
430
431							if ( result && result['status'] === 'ok' && result['nonce'] )
432							{
433								BookneticData.ajax_nonce = result['nonce'];
434							}
435						} ).always( function ()
436						{
437							window.bookneticNonceRefreshingPromise = null;
438							resolve();
439						} );
440					} );
441				}
442
443				return window.bookneticNonceRefreshingPromise.then( callback );
444			},
445
446			ensureFreshNonce: function ( callback )
447			{
448				callback = typeof callback === 'function' ? callback : function(){};
449
450				if ( window.bookneticNonceInitialized )
451				{
452					callback();
453					return;
454				}
455
456				window.bookneticNonceInitialized = true;
457				booknetic.refreshNonce( callback );
458			},
459
460			ajax: function ( action , params , func , loading, fnOnError, async_param )
461			{
462				// helper: best-effort JSON parse
463				const tryParse = (x) => { try { return JSON.parse(x); } catch { return x; } };
464
465				// backward-compatible defaults
466				params      = typeof params      === 'undefined' ? {}            : params;
467				func        = typeof func        === 'function'   ? func          : function(){};
468				loading     = loading !== false;
469				fnOnError   = typeof fnOnError   === 'function'   ? fnOnError     : function(){};
470				async_param = typeof async_param === 'undefined' ? true          : async_param;
471
472				const endLoader = () => { if ( loading ) booknetic.loading( 0 ); };
473
474				/* -------------------------------------------------------------
475				   Resolve backend / frontend identifiers
476				------------------------------------------------------------- */
477				const { backend, frontend } = ( action instanceof Object )
478					? { backend: action[ 'backend_action' ], frontend: action[ 'frontend_action' ] }
479					: { backend: action, frontend: action };
480
481				/* -------------------------------------------------------------
482				   Prepare payload
483				------------------------------------------------------------- */
484				const tenantId   = BookneticData.tenant_id || booking_panel_js.data( 'tenant_id' );
485				const isFormData = params instanceof FormData;
486
487				if ( isFormData )
488				{
489					params.append( 'action',    'bkntc_' + backend );
490					params.append( 'tenant_id', tenantId );
491					params.append( '_ajax_nonce', BookneticData.ajax_nonce );
492				}
493				else
494				{
495					params = { action: 'bkntc_' + backend, tenant_id: tenantId, _ajax_nonce: BookneticData.ajax_nonce, ...params };
496				}
497
498				/* -------------------------------------------------------------
499				   Hooks (before) and loader
500				------------------------------------------------------------- */
501				bookneticHooks.doAction( 'ajax_before_' + frontend, params, booknetic );
502				params = bookneticHooks.doFilter( 'ajax', params, booknetic );
503				params = bookneticHooks.doFilter( 'ajax_' + frontend, params, booknetic );
504
505				if ( loading )
506				{
507					booknetic.loading( true );
508				}
509
510				/* -------------------------------------------------------------
511				   Execute request
512				------------------------------------------------------------- */
513				let nonceRetried = false;
514
515				const applyNonce = () =>
516				{
517					if ( isFormData )
518						params.set( '_ajax_nonce', BookneticData.ajax_nonce );
519					else
520						params[ '_ajax_nonce' ] = BookneticData.ajax_nonce;
521				};
522
523				const performRequest = () =>
524				{
525					$.ajax( {
526						url:    BookneticData.ajax_url,
527						method: 'POST',
528						data:   params,
529						async:  async_param,
530						...( isFormData ? { processData: false, contentType: false } : {} )
531					} ).done( function ( rawResult )
532					{
533						endLoader();
534
535						const result = tryParse( rawResult );
536						const ok     = booknetic.ajaxResultCheck( result, fnOnError );
537
538						if ( ok )
539						{
540							func( result );
541							bookneticHooks.doAction( 'ajax_after_' + frontend + '_success', booknetic, params, result );
542						}
543						else
544						{
545							fnOnError( result );
546							bookneticHooks.doAction( 'ajax_after_' + frontend + '_error', booknetic, params, result );
547						}
548					} ).fail( function ( jqXHR )
549					{
550						if ( jqXHR.status === 403 && ! nonceRetried )
551						{
552							nonceRetried = true;
553							booknetic.refreshNonce( function ()
554							{
555								applyNonce();
556								performRequest();
557							} );
558							return;
559						}
560
561						endLoader();
562						booknetic.toast( jqXHR.status + ' error!' );
563						fnOnError({ status: 'error', error_msg: jqXHR.status + ' error!', jqXHR: jqXHR });
564						bookneticHooks.doAction( 'ajax_after_' + frontend + '_error', booknetic, params );
565					} );
566				};
567
568				performRequest();
569			},
570
571			ajaxAsync: function ( action , params , loading, async_param )
572			{
573				return new Promise( function ( resolve, reject )
574				{
575					booknetic.ajax(
576						action,
577						params,
578						function( result )
579						{
580							resolve( result );
581						},
582						loading,
583						function ( error )
584						{
585							reject( error );
586						},
587						async_param
588					);
589				});
590			},
591
592			ajaxRest: (endpoint, method = "GET", data = {}, {noLoading = false} = {}) => {
593				if (!noLoading) booknetic.loading(true);
594
595				return $.ajax({
596					url: restURL + endpoint,
597					method,
598					data,
599					headers: {"X-Wp-Nonce": BookneticData.rest_nonce},
600				})
601					.always(() => {
602						if (!noLoading) booknetic.loading(false);
603					})
604					.fail((jqXHR) => {
605						booknetic.toast(jqXHR.status + " error!", "unsuccess");
606					});
607			},
608
609			select2Ajax: function ( select, action, parameters )
610			{
611				var params = {};
612				params['action'] = 'bkntc_' + action;
613				params['tenant_id'] = BookneticData.tenant_id || booking_panel_js.data('tenant_id');
614				params['_ajax_nonce'] = BookneticData.ajax_nonce;
615
616				select.select2({
617					theme: 'bootstrap',
618					placeholder: __('select'),
619					language: {
620						searching: function() {
621							return __('searching');
622						}
623					},
624					allowClear: true,
625					ajax: {
626						url: BookneticData.ajax_url,
627						dataType: 'json',
628						type: "POST",
629						data: function ( q )
630						{
631							var sendParams = params;
632							sendParams['q'] = q['term'];
633
634							if( typeof parameters == 'function' )
635							{
636								var additionalParameters = parameters( $(this) );
637
638								for (let key in additionalParameters)
639								{
640									sendParams[key] = additionalParameters[key];
641								}
642							}
643							else if( typeof parameters == 'object' )
644							{
645								for (let key in parameters)
646								{
647									sendParams[key] = parameters[key];
648								}
649							}
650
651							return sendParams;
652						},
653						processResults: function ( result )
654						{
655							if( booknetic.ajaxResultCheck( result ) )
656							{
657								try
658								{
659									result = JSON.parse(result);
660								}
661								catch(e)
662								{
663
664								}
665
666								return result;
667							}
668						}
669					}
670				});
671			},
672
673			zeroPad: function(n, p)
674			{
675				p = p > 0 ? p : 2;
676
677				n = String(n);
678				while (n.length < p)
679					n = '0' + n;
680
681				return n;
682			},
683
684			toast: function( title )
685			{
686				if( title === false )
687				{
688					booking_panel_js.find('.booknetic_warning_message').fadeOut(200);
689					return;
690				}
691
692				booking_panel_js.find('.booknetic_warning_message').text( booknetic.htmlspecialchars_decode( title, 'ENT_QUOTES' ) ).fadeIn(300);
693				setTimeout(function ()
694				{
695					booking_panel_js.find('.booknetic_warning_message').fadeOut(200);
696				}, 5000);
697			},
698
699			nonRecurringCalendar: function ( year , month, load_dates_from_backend, load_calendar )
700			{
701				load_calendar = !!load_calendar;
702
703				const now = new Date();
704
705				year  = year ?? now.getFullYear();
706				month = month ?? now.getMonth();
707
708				booknetic.loadDefaultDate( year, month, load_calendar );
709
710				if ( load_dates_from_backend )
711					booknetic.loadDateFromBackend( year, month, load_calendar );
712			},
713
714			loadDefaultDate: function( year, month, load )
715			{
716				booknetic.calendarYear = year;
717				booknetic.calendarMonth = month;
718
719				booknetic.displayCalendar( load );
720				booknetic.displayBringPeopleSelect();
721			},
722
723			loadDateFromBackend: function( year, month, load )
724			{
725				booknetic.ajax( 'get_data', booknetic.ajaxParameters( {
726					current_step: 'date_time',
727					year:   year,
728					month:  month + 1,
729					info: booking_panel_js.data( 'info' )
730				} ), function ( result )
731				{
732					booknetic.calendarDateTimes = result[ 'data' ];
733					booknetic.time_show_format = result[ 'time_show_format' ];
734
735					booknetic.calendarYear = result[ 'calendar_start_year' ];
736					booknetic.calendarMonth = result[ 'calendar_start_month' ] - 1;
737
738					booknetic.displayCalendar( load );
739					booknetic.displayBringPeopleSelect();
740
741					booknetic.addGroupAppointmentsCounterForBookneticCalendarDays();
742				} , load );
743			},
744
745			displayBringPeopleSelect: function()
746			{
747				var select = booking_panel_js.find('.booknetic_number_of_brought_customers select');
748
749				var options = '';
750
751				for(var i = 1; i < booknetic.serviceMaxCapacity; i++ )
752				{
753					options += '<option value="' + i + '"> + ' + i + '</option>'
754				}
755
756				select.html( options );
757
758			},
759
760			displayCalendar: function( loader )
761			{
762				var _year = booknetic.calendarYear;
763				var _month = booknetic.calendarMonth;
764
765				var htmlContent		= "",
766					febNumberOfDays	= "",
767					counter			= 1,
768					dateNow			= new Date(_year , _month ),
769					month			= dateNow.getMonth()+1,
770					year			= dateNow.getFullYear(),
771					currentDate		= new Date();
772
773				if (month == 2)
774				{
775					febNumberOfDays = ( (year%100!=0) && (year%4==0) || (year%400==0)) ? '29' : '28';
776				}
777
778				var monthNames	= booknetic.localization.month_names;
779				var dayPerMonth	= [null, '31', febNumberOfDays ,'31','30','31','30','31','31','30','31','30','31']
780
781				var nextDate	= new Date(month +'/01/'+year);
782				var weekdays	= nextDate.getDay();
783
784				let weekdays2;
785				let week_start_n;
786				let week_end_n;
787
788				if( BookneticData.week_starts_on === 'monday' )
789				{
790					weekdays2	= weekdays == 0 ? 7 : weekdays;
791					week_start_n = 1;
792					week_end_n = 7;
793				}
794				else
795				{
796					weekdays2	= weekdays;
797					week_start_n = 0;
798					week_end_n = 6;
799				}
800
801				var numOfDays	= dayPerMonth[month];
802
803				for( let w = week_start_n; w < weekdays2; w++ )
804				{
805					htmlContent += "<div class=\"booknetic_td booknetic_empty_day\"></div>";
806				}
807
808				while (counter <= numOfDays)
809				{
810					if (weekdays2 > week_end_n)
811					{
812						weekdays2 = week_start_n;
813						htmlContent += "</div><div class=\"booknetic_calendar_rows\">";
814					}
815					var date_formatted = year + '-' + booknetic.zeroPad(month) + '-' + booknetic.zeroPad(counter);
816					let date_format_view;
817
818					if( BookneticData.date_format === 'Y-m-d' )
819					{
820						date_format_view = year + '-' + booknetic.zeroPad(month) + '-' + booknetic.zeroPad(counter);
821					}
822					else if( BookneticData.date_format === 'd-m-Y' )
823					{
824						date_format_view = booknetic.zeroPad(counter) + '-' + booknetic.zeroPad(month) + '-' + year;
825					}
826					else if( BookneticData.date_format === 'm/d/Y' )
827					{
828						date_format_view = booknetic.zeroPad(month) + '/' + booknetic.zeroPad(counter) + '/' + year;
829					}
830					else if( BookneticData.date_format === 'd/m/Y' )
831					{
832						date_format_view = booknetic.zeroPad(counter) + '/' + booknetic.zeroPad(month) + '/' + year;
833					}
834					else if( BookneticData.date_format === 'd.m.Y' )
835					{
836						date_format_view = booknetic.zeroPad(counter) + '.' + booknetic.zeroPad(month) + '.' + year;
837					}
838
839					var addClass = '';
840					if( !(date_formatted in booknetic.calendarDateTimes['dates']) || booknetic.calendarDateTimes['dates'][ date_formatted ].length == 0 )
841					{
842						addClass = ' booknetic_calendar_empty_day';
843					}
844
845					var loadLine = booknetic.drawLoadLine( date_formatted );
846
847					htmlContent +="<div class=\"booknetic_td booknetic_calendar_days"+addClass+"\" data-date=\"" + date_formatted + "\" data-date-format=\"" + date_format_view + "\"><div>"+counter+"<span>" + loadLine + "</span></div></div>";
848
849					weekdays2++;
850					counter++;
851				}
852
853				for( let w = weekdays2; w <= week_end_n; w++ )
854				{
855					htmlContent += "<div class=\"booknetic_td booknetic_empty_day\"></div>";
856				}
857
858				var calendarBody = "<div class=\"booknetic_calendar\">";
859
860				calendarBody += "<div class=\"booknetic_calendar_rows booknetic_week_names\">";
861
862				for( var w = 0; w < booknetic.localization.day_of_week.length; w++ )
863				{
864					if( w > week_end_n || w < week_start_n )
865						continue;
866
867					calendarBody += "<div class=\"booknetic_td\">" + booknetic.localization.day_of_week[ w ] + "</div>";
868				}
869
870				calendarBody += "</div>";
871
872				calendarBody += "<div class=\"booknetic_calendar_rows\">";
873				calendarBody += htmlContent;
874				calendarBody += "</div></div>";
875
876				booking_panel_js.find("#booknetic_calendar_area").html( calendarBody );
877
878				booking_panel_js.find("#booknetic_calendar_area .days[data-count]:first").trigger('click');
879
880				booking_panel_js.find(".booknetic_month_name").text( monthNames[ _month ] + ' ' + _year );
881				booking_panel_js.find('.booknetic_times_list').empty();
882				booking_panel_js.find('.booknetic_times_title').text(__('Select date'));
883
884				if( !loader )
885				{
886					booking_panel_js.find(".booknetic_preloader_card3_box").hide();
887
888					booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="date_time"]').fadeIn(200, function()
889					{
890						booking_panel_js.find(".booknetic_appointment_container_body").scrollTop(0);
891						booknetic.handleScroll();
892					});
893				}
894			},
895
896			drawLoadLine: function( date )
897			{
898				let zoom = function (input, outputSize) {
899					if (input.length === outputSize) {
900						return input;
901					}
902					const ratio = outputSize / input.length;
903
904					const output = new Array(outputSize);
905
906					for (let i = 0; i < outputSize; i++) {
907						let value = false;
908
909						const from = i / ratio;
910						const inc = Math.max(1, 1 / ratio);
911
912						for (let j = Math.floor(from); j < Math.floor(from + inc); j++) {
913							value = value || input[j];
914						}
915
916						output[i] = value;
917					}
918
919					return output;
920				};
921
922				var fills = date in booknetic.calendarDateTimes['fills'] ? booknetic.calendarDateTimes['fills'][ date ] : [0];
923				var data = date in booknetic.calendarDateTimes['dates'] ? booknetic.calendarDateTimes['dates'][ date ] : [];
924				if (data.length === 1 && booknetic.dateBasedService && !( 'hide_available_slots' in booknetic.calendarDateTimes && booknetic.calendarDateTimes['hide_available_slots'] === 'on' )) {
925					fills = [];
926					for (let i = 0; i < data[0].max_capacity; i++) {
927						fills.push(data[0].max_capacity - data[0].weight - i > 0 ? 1 : 0);
928					}
929				}
930
931				var day_schedule = zoom(fills, 17);
932
933				var line = '';
934				for( var j = 0; j < day_schedule.length; j++ )
935				{
936					var isFree = day_schedule[j];
937					line += '<i '+ (isFree?'a':'b') + '></i>';
938				}
939
940				return line;
941			},
942
943			timeToMin: function(str)
944			{
945				str = str.split(':');
946
947				return parseInt(str[0]) * 60 + parseInt(str[1]);
948			},
949
950			timeZoneOffset: function()
951			{
952				if( BookneticData.client_time_zone === 'off' )
953					return  '-';
954
955				if ( window.Intl && typeof window.Intl === 'object' )
956				{
957					return Intl.DateTimeFormat().resolvedOptions().timeZone;
958				}
959				else
960				{
961					return new Date().getTimezoneOffset();
962				}
963			},
964
965			datePickerFormat: function()
966			{
967				if( BookneticData.date_format === 'd-m-Y' )
968				{
969					return 'dd-mm-yyyy';
970				}
971				else if( BookneticData.date_format === 'm/d/Y' )
972				{
973					return 'mm/dd/yyyy';
974				}
975				else if( BookneticData.date_format === 'd/m/Y' )
976				{
977					return 'dd/mm/yyyy';
978				}
979				else if( BookneticData.date_format === 'd.m.Y' )
980				{
981					return 'dd.mm.yyyy';
982				}
983
984				return 'yyyy-mm-dd';
985			},
986
987			convertDate: function( date, from, to )
988			{
989				if( date == '' )
990					return date;
991				if( typeof to === 'undefined' )
992				{
993					to = booknetic.datePickerFormat();
994				}
995
996				to = to.replace('yyyy', 'Y').replace('dd', 'd').replace('mm', 'm');
997				from = from.replace('yyyy', 'Y').replace('dd', 'd').replace('mm', 'm');
998
999				var delimetr = from.indexOf('-') > -1 ? '-' : ( from.indexOf('.') > -1 ? '.' : '/' );
1000				var delimetr_to = to.indexOf('-') > -1 ? '-' : ( to.indexOf('.') > -1 ? '.' : '/' );
1001				var date_split = date.split(delimetr);
1002				var date_from_split = from.split(delimetr);
1003				var date_to_split = to.split(delimetr_to);
1004
1005				var parts = {'m':0, 'd':0, 'Y':0};
1006
1007				date_from_split.forEach(function( val, i )
1008				{
1009					parts[ val ] = i;
1010				});
1011
1012				var new_date = '';
1013				date_to_split.forEach(function( val, j )
1014				{
1015					new_date += (new_date == '' ? '' : delimetr_to) + date_split[ parts[ val ] ];
1016				});
1017
1018				return new_date;
1019			},
1020
1021			getSelected: {
1022
1023				location: function()
1024				{
1025					if( booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="location"]').hasClass('booknetic_menu_hidden') )
1026					{
1027						var val = booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="location"]').data('value');
1028					}
1029					else
1030					{
1031						val = booking_panel_js.find(".booknetic_appointment_container_body [data-step-id=\"location\"] .booknetic_card_selected").data('id');
1032					}
1033
1034					return val ? val : '';
1035				},
1036
1037				locationCategory: function()
1038				{
1039					return booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="location"]').data('location-category');
1040				},
1041
1042				staff: function()
1043				{
1044					if( booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="staff"]').hasClass('booknetic_menu_hidden') )
1045					{
1046						var val = booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="staff"]').data('value');
1047					}
1048					else
1049					{
1050						val = booking_panel_js.find(".booknetic_appointment_container_body [data-step-id=\"staff\"] .booknetic_card_selected").data('id');
1051					}
1052
1053					return val ? val : '';
1054				},
1055
1056				service: function()
1057				{
1058					const serviceId = booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="service"]').data('value');
1059
1060					if( serviceId )
1061					{
1062						var val = serviceId;
1063					}
1064					else
1065					{
1066						val = booking_panel_js.find(".booknetic_appointment_container_body [data-step-id=\"service\"] .booknetic_service_card_selected").data('id');
1067					}
1068
1069					return val ? val : '';
1070				},
1071
1072				serviceCategory: function()
1073				{
1074					return booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="service"]').data('service-category');
1075				},
1076
1077				serviceIsRecurring: function()
1078				{
1079					let val;
1080
1081					if( booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="service"]').hasClass('booknetic_menu_hidden') )
1082					{
1083						val = booking_panel_js.find( '.booknetic_appointment_step_element[data-step-id="service"]' ).data( 'is-recurring' );
1084					}
1085					else
1086					{
1087						val = booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="service"] .booknetic_service_card_selected').data('is-recurring');
1088					}
1089
1090					return val == '1';
1091				},
1092
1093				serviceExtras: function()
1094				{
1095					var extras = [];
1096
1097					booking_panel_js.find(".booknetic_appointment_container_body [data-step-id=\"service_extras\"]  .booknetic_service_extra_card_selected").each(function()
1098					{
1099						var extra_id	 = $(this).data('id'),
1100							quantity    = parseInt( $(this).find('.booknetic_service_extra_quantity_input').val() );
1101
1102						if( quantity > 0  )
1103						{
1104							extras.push({
1105								extra: extra_id,
1106								quantity: quantity,
1107							});
1108						}
1109					});
1110
1111					return extras;
1112				},
1113
1114				date: function()
1115				{
1116					if( booknetic.getSelected.serviceIsRecurring() )
1117						return '';
1118
1119					var val = booking_panel_js.find(".booknetic_selected_time").data('date-original');
1120					return val ? val : '';
1121				},
1122
1123				date_in_customer_timezone: function()
1124				{
1125					if( booknetic.getSelected.serviceIsRecurring() )
1126						return '';
1127
1128					var val = booking_panel_js.find(".booknetic_calendar_selected_day").data('date');
1129					return val ? val : '';
1130				},
1131
1132				time: function()
1133				{
1134					if( booknetic.getSelected.serviceIsRecurring() )
1135						return booknetic.getSelected.recurringTime();
1136
1137					var val = booking_panel_js.find(".booknetic_selected_time").data('time');
1138					return val ? val : '';
1139				},
1140
1141				brought_people_count: function()
1142				{
1143					if( ! booking_panel_js.find('#booknetic_bring_someone_checkbox ').is(':checked') )
1144						return 0;
1145
1146					let broughtPeopleInput = booking_panel_js.find('.booknetic_number_of_brought_customers_quantity_input');
1147					let val = Number( broughtPeopleInput.val() );
1148					let max = Number( broughtPeopleInput.data( 'max-quantity' ) );
1149
1150					val = Number.isInteger( val ) ? val : 0;
1151
1152					return val > max ? max : val;
1153				},
1154
1155				dateTime: function()
1156				{
1157					if( booknetic.getSelected.serviceIsRecurring() )
1158						return booknetic.getSelected.recurringTime();
1159
1160					var val = booking_panel_js.find(".booknetic_selected_time").data('full-date-time-start');
1161					return val ? val : '';
1162				},
1163
1164				formData: function ()
1165				{
1166					var data = { data: {email: "", first_name: "", last_name: "", phone: ""} };
1167
1168					booking_panel_js.find('input[name]#bkntc_input_name, input[name]#bkntc_input_surname, input[name]#bkntc_input_email, input[name]#bkntc_input_phone').each(function()
1169					{
1170						let name	= $(this).attr('name');
1171                        let value   = $(this).val();
1172
1173                        if(name === 'phone'){
1174                            const itiInstance = booking_panel_js.find('#bkntc_input_phone').data('iti');
1175                            value = itiInstance.getNumber(bookneticIntlTelInput.utils?.numberFormat?.E164 ?? 0);
1176                        }
1177
1178						if ( name === 'email' )
1179							value = value.trim()
1180
1181						data['data'][name] = value;
1182					});
1183
1184					return data;
1185				},
1186
1187				customerId: function ()
1188				{
1189					let customerId = booking_panel_js.find('.bkntc_input_identifier_input').data('customer-id');
1190
1191					return customerId || 0;
1192				},
1193
1194				paymentMethod: function ()
1195				{
1196					if( booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="confirm_details"]').hasClass('booknetic_menu_hidden') )
1197						return 'local';
1198
1199					return booking_panel_js.find('.booknetic_payment_method.booknetic_payment_method_selected').attr('data-payment-type');
1200				},
1201
1202				paymentDepositFullAmount: function ()
1203				{
1204					return booking_panel_js.find('input[name="input_deposit"][type="radio"]:checked').val() == '0' ? true : false;
1205				},
1206
1207				recurringStartDate: function()
1208				{
1209					var val = booking_panel_js.find("#booknetic_recurring_start").val();
1210
1211					if( val == '' || val == undefined )
1212						return '';
1213
1214					return booknetic.convertDate( val, booknetic.datePickerFormat(), 'Y-m-d' );
1215				},
1216
1217				recurringEndDate: function()
1218				{
1219					var val = booking_panel_js.find("#booknetic_recurring_end").val();
1220
1221					if( val == '' || val == undefined )
1222						return '';
1223
1224					return booknetic.convertDate( val, booknetic.datePickerFormat(), 'Y-m-d' );
1225				},
1226
1227				recurringTimesArr: function()
1228				{
1229					if( !booknetic.serviceData )
1230						return JSON.stringify( {} );
1231
1232					var repeatType		=	booknetic.serviceData['repeat_type'],
1233						recurringTimes	=	{};
1234
1235					if( repeatType == 'weekly' )
1236					{
1237						booking_panel_js.find(".booknetic_times_days_of_week_area > .booknetic_active_day").each(function()
1238						{
1239							var dayNum = $(this).data('day');
1240							var time = $(this).find('.booknetic_wd_input_time').val();
1241
1242							recurringTimes[ dayNum ] = time;
1243						});
1244
1245						recurringTimes = JSON.stringify( recurringTimes );
1246					}
1247					else if( repeatType == 'daily' )
1248					{
1249						recurringTimes = booking_panel_js.find("#booknetic_daily_recurring_frequency").val();
1250					}
1251					else if( repeatType == 'monthly' )
1252					{
1253						recurringTimes = booking_panel_js.find("#booknetic_monthly_recurring_type").val();
1254						if( recurringTimes == 'specific_day' )
1255						{
1256							recurringTimes += ':' + ( booking_panel_js.find("#booknetic_monthly_recurring_day_of_month").val() == null ? '' : booking_panel_js.find("#booknetic_monthly_recurring_day_of_month").val().join(',') );
1257						}
1258						else
1259						{
1260							recurringTimes += ':' + booking_panel_js.find("#booknetic_monthly_recurring_day_of_week").val();
1261						}
1262					}
1263
1264					return recurringTimes;
1265				},
1266
1267				recurringTimesArrFinish: function()
1268				{
1269					var recurringDates = [];
1270					var hasTimeError = false;
1271
1272					booking_panel_js.find("#booknetic_recurring_dates > tr").each(function()
1273					{
1274						var sDate = $(this).find('[data-date]').attr('data-date');
1275						var sTime = $(this).find('[data-time]').attr('data-time');
1276						if($(this).find('[data-service-type]').attr('data-service-type') === 'datebased')
1277						{
1278							sTime = '00:00';
1279						}
1280						// if tried to change the time
1281						if( $(this).find('.booknetic_time_select').length )
1282						{
1283							sTime = $(this).find('.booknetic_time_select').val();
1284							if( sTime == '' )
1285							{
1286								hasTimeError = true;
1287							}
1288						}
1289						else if( $(this).find('.booknetic_data_has_error').length > 0 )
1290						{
1291							hasTimeError = ! booknetic.dateBasedService;
1292						}
1293
1294						recurringDates.push([ sDate, sTime ]);
1295					});
1296
1297					if( hasTimeError )
1298					{
1299						return false;
1300					}
1301
1302					return JSON.stringify( recurringDates );
1303				},
1304
1305				recurringDateValidate: function()
1306				{
1307					let dateError = true;
1308					booking_panel_js.find("#booknetic_recurring_dates > tr").each(function()
1309					{
1310						if( $(this).find('td[data-date] span.booknetic_data_has_error').length > 0 )
1311						{
1312							dateError =  false;
1313						}
1314					});
1315					return dateError;
1316				},
1317
1318				recurringTime: function ()
1319				{
1320					if( !booknetic.serviceData )
1321						return  '';
1322
1323					var repeatType	=	booknetic.serviceData['repeat_type'],
1324						time		=	'';
1325
1326					if( repeatType == 'daily' )
1327					{
1328						time = booking_panel_js.find("#booknetic_daily_time").val();
1329					}
1330					else if( repeatType == 'monthly' )
1331					{
1332						time = booking_panel_js.find("#booknetic_monthly_time").val();
1333					}
1334
1335					return time;
1336				}
1337
1338			},
1339
1340			ajaxParameters: function ( defaultData = undefined , bool = true )
1341			{
1342				var data = new FormData();
1343
1344				data.append( 'payment_method', booknetic.getSelected.paymentMethod() );
1345				data.append( 'deposit_full_amount', booknetic.getSelected.paymentDepositFullAmount() ? 1 : 0 );
1346				data.append( 'client_time_zone', booknetic.timeZoneOffset() );
1347
1348				data.append( 'google_recaptcha_token', booknetic.google_recaptcha_token );
1349				data.append( 'google_recaptcha_action', booknetic.google_recaptcha_action );
1350
1351				if( typeof defaultData != 'undefined' )
1352				{
1353					for ( var key in defaultData )
1354					{
1355						data.append( key, defaultData[key] );
1356					}
1357				}
1358
1359				if ( bool )
1360				{
1361					this.stepManager.saveData();
1362				}
1363
1364				data.append( 'cart', JSON.stringify(booknetic.cartArr) );
1365				data.append( 'current', booknetic.cartCurrentIndex );
1366				data.append( 'query_params', booknetic.getURLQueryParams() );
1367
1368				return bookneticHooks.doFilter( 'appointment_ajax_data', data, booknetic );
1369			},
1370
1371			getURLQueryParams: function()
1372			{
1373				const queryString = window.location.search;
1374				const searchParams = new URLSearchParams( queryString );
1375
1376				let query_params = {};
1377
1378				searchParams.forEach( ( value, key ) => {
1379					query_params[ key ] = value;
1380				} );
1381
1382				return JSON.stringify( query_params );
1383			},
1384
1385			calcRecurringTimes: function()
1386			{
1387				booknetic.serviceFixPeriodEndDate();
1388
1389				var fullPeriod			=	booknetic.serviceData['full_period_value'];
1390				var repeatType			=	booknetic.serviceData['repeat_type'];
1391				var startDate			=	booknetic.getSelected.recurringStartDate();
1392				var endDate				=	booknetic.getSelected.recurringEndDate();
1393
1394				if( startDate == '' || endDate == '' )
1395					return;
1396
1397				endDate		= booknetic.getDateWithUTC( endDate );
1398
1399				var cursor	= booknetic.getDateWithUTC( startDate ),
1400					numberOfAppointments = 0,
1401					frequency = ( repeatType === 'daily' ) ? booking_panel_js.find( '#booknetic_daily_recurring_frequency' ).val() : 1;
1402
1403				if( !( frequency >= 1 ) )
1404				{
1405					frequency = 1;
1406					if( repeatType === 'daily' )
1407					{
1408						booking_panel_js.find('#booknetic_daily_recurring_frequency').val('1');
1409					}
1410				}
1411
1412				var activeDays = {};
1413				if( repeatType === 'weekly' )
1414				{
1415					booking_panel_js.find(".booknetic_times_days_of_week_area > .booknetic_active_day").each(function()
1416					{
1417						activeDays[ $(this).data('day') ] = true;
1418					});
1419
1420					if( $.isEmptyObject( activeDays ) )
1421					{
1422						return;
1423					}
1424				}
1425				else if( repeatType === 'monthly' )
1426				{
1427					var monthlyRecurringType = booking_panel_js.find("#booknetic_monthly_recurring_type").val();
1428					var monthlyDayOfWeek = booking_panel_js.find("#booknetic_monthly_recurring_day_of_week").val();
1429
1430					var selectedDays = booking_panel_js.find("#booknetic_monthly_recurring_day_of_month").select2('val');
1431
1432					if( selectedDays )
1433					{
1434						for( var i = 0; i < selectedDays.length; i++ )
1435						{
1436							activeDays[ selectedDays[i] ] = true;
1437						}
1438					}
1439				}
1440
1441				while( cursor <= endDate )
1442				{
1443					var weekNum = cursor.getDay();
1444					//todo://why did we use parseInt here and on many other places? busy with other tasks rn
1445					// test it out in the future.
1446					var dayNumber = parseInt( cursor.getDate() );
1447					weekNum = weekNum > 0 ? weekNum : 7;
1448					var dateFormat = cursor.getFullYear() + '-' + booknetic.zeroPad( cursor.getMonth() + 1 ) + '-' + booknetic.zeroPad( cursor.getDate() );
1449
1450					if( repeatType === 'monthly' )
1451					{
1452						if( ( monthlyRecurringType === 'specific_day' && typeof activeDays[ dayNumber ] != 'undefined' ) || booknetic.getMonthWeekInfo( cursor, monthlyRecurringType, monthlyDayOfWeek ) )
1453						{
1454							if(
1455								// if is not off day for staff or service
1456								!( typeof booknetic.globalTimesheet[ weekNum-1 ] != 'undefined' && booknetic.globalTimesheet[ weekNum-1 ]['day_off'] ) &&
1457								// if is not holiday for staff or service
1458								typeof booknetic.globalDayOffs[ dateFormat ] == 'undefined'
1459							)
1460							{
1461								numberOfAppointments++;
1462							}
1463						}
1464					}
1465					else if(
1466						// if weekly repeat type then only selected days of the week...
1467						( typeof activeDays[ weekNum ] != 'undefined' || repeatType === 'daily' ) &&
1468						// if is not off day for staff or service
1469						!( typeof booknetic.globalTimesheet[ weekNum-1 ] != 'undefined' && booknetic.globalTimesheet[ weekNum-1 ]['day_off'] ) &&
1470						// if is not holiday for staff or service
1471						typeof booknetic.globalDayOffs[ dateFormat ] == 'undefined'
1472					)
1473					{
1474						numberOfAppointments++;
1475					}
1476
1477					cursor = new Date( cursor.getTime() + 1000 * 24 * 3600 * frequency );
1478				}
1479
1480				booking_panel_js.find('#booknetic_recurring_times').val( numberOfAppointments );
1481
1482			},
1483
1484			initRecurringElements: function( )
1485			{
1486				booknetic.select2Ajax( booking_panel_js.find(".booknetic_wd_input_time, #booknetic_daily_time, #booknetic_monthly_time"), 'get_available_times_all', function( select )
1487				{
1488					var dayNumber = ( select.attr( 'id' ) === 'booknetic_daily_time' || select.attr( 'id' ) === 'booknetic_monthly_time' ) ? -1 : select.attr( 'id' ).replace( 'booknetic_time_wd_', '' );
1489
1490					return booknetic.formDataToObject( booknetic.ajaxParameters({day_number: dayNumber}) );
1491				});
1492
1493				booking_panel_js.find("#booknetic_monthly_recurring_day_of_month").select2({
1494					theme: 'bootstrap',
1495					placeholder: __('select'),
1496					allowClear: true,
1497					maximumSelectionLength: booknetic.serviceData[ 'repeat_frequency' ],
1498					closeOnSelect: false,
1499				}).on( 'select2:select', function( e )
1500				{
1501					// https://github.com/select2/select2/issues/3514
1502
1503					if (
1504						$( this ).select2( "data" ).length >=
1505						$( this ).data( "select2" ).results.data.maximumSelectionLength
1506					)
1507					{
1508						$( this ).select2( "close" );
1509					}
1510				});
1511
1512				booking_panel_js.find("#booknetic_monthly_recurring_type, #booknetic_monthly_recurring_day_of_week").select2({
1513					theme: 'bootstrap',
1514					placeholder: __('select'),
1515					minimumResultsForSearch: -1
1516				});
1517
1518				booking_panel_js.find('#booknetic_monthly_recurring_type').trigger('change');
1519
1520				booknetic.initDatepicker( booking_panel_js.find("#booknetic_recurring_start") );
1521				booknetic.initDatepicker( booking_panel_js.find("#booknetic_recurring_end") );
1522
1523				booknetic.serviceFixPeriodEndDate();
1524				booknetic.serviceFixFrequency();
1525				booking_panel_js.find("#booknetic_recurring_start").trigger('change');
1526			},
1527
1528			loadAvailableDate: function(instance ,data)
1529			{
1530				booknetic.ajax( 'get_recurring_available_dates', data, function ( result )
1531				{
1532					instance.set('enable',result['available_dates']);
1533				});
1534			},
1535
1536			serviceFixPeriodEndDate: function()
1537			{
1538				let startDate, endDate;
1539				let serviceData = booknetic.serviceData;
1540
1541				if( serviceData && serviceData['full_period_value'] > 0 )
1542				{
1543					booking_panel_js.find("#booknetic_recurring_end").attr('disabled', true);
1544					booking_panel_js.find("#booknetic_recurring_times").attr('disabled', true);
1545
1546					startDate = booknetic.getSelected.recurringStartDate();
1547
1548					if( serviceData[ 'full_period_type' ] === 'month' )
1549					{
1550						endDate = new Date( startDate + "T00:00:00" );
1551						endDate.setMonth( endDate.getMonth() + parseInt( serviceData['full_period_value'] ) );
1552						endDate.setDate( endDate.getDate() - 1 );
1553
1554						booking_panel_js.find("#booknetic_recurring_end").val( booknetic.convertDate( endDate.getFullYear() + '-' + booknetic.zeroPad( endDate.getMonth() + 1 ) + '-' + booknetic.zeroPad( endDate.getDate() ), 'Y-m-d' ) );
1555					}
1556					else if( serviceData[ 'full_period_type' ] === 'week' )
1557					{
1558
1559						endDate = new Date( startDate + "T00:00:00" );
1560						endDate.setDate( endDate.getDate() + parseInt( serviceData['full_period_value'] ) * 7 - 1 );
1561
1562						booking_panel_js.find("#booknetic_recurring_end").val( booknetic.convertDate( endDate.getFullYear() + '-' + booknetic.zeroPad( endDate.getMonth() + 1 ) + '-' + booknetic.zeroPad( endDate.getDate() ), 'Y-m-d' ) );
1563					}
1564					else if( serviceData[ 'full_period_type' ] === 'day' )
1565					{
1566						endDate = new Date( startDate + "T00:00:00" );
1567						endDate.setDate( endDate.getDate() + parseInt( serviceData['full_period_value'] ) - 1 );
1568
1569						booking_panel_js.find("#booknetic_recurring_end").val( booknetic.convertDate( endDate.getFullYear() + '-' + booknetic.zeroPad( endDate.getMonth() + 1 ) + '-' + booknetic.zeroPad( endDate.getDate() ), 'Y-m-d' ) );
1570					}
1571					else if( serviceData[ 'full_period_type' ] === 'time' )
1572					{
1573						if( booknetic.getSelected.recurringEndDate() == '' )
1574						{
1575							startDate = new Date( booknetic.getSelected.recurringStartDate() );
1576							endDate = new Date( startDate.setMonth( startDate.getMonth() + 1 ) );
1577
1578							booking_panel_js.find("#booknetic_recurring_end").val( booknetic.convertDate( endDate.getFullYear() + '-' + booknetic.zeroPad( endDate.getMonth() + 1 ) + '-' + booknetic.zeroPad( endDate.getDate() ), 'Y-m-d' ) );
1579						}
1580
1581						booking_panel_js.find("#booknetic_recurring_times").val( serviceData['full_period_value'] ).trigger('keyup');
1582					}
1583				}
1584				else
1585				{
1586					booking_panel_js.find("#booknetic_recurring_end").attr('disabled', false);
1587					booking_panel_js.find("#booknetic_recurring_times").attr('disabled', false);
1588
1589					if( booknetic.getSelected.recurringEndDate() == '' )
1590					{
1591						startDate = new Date( booknetic.getSelected.recurringStartDate() );
1592						endDate = new Date( startDate.setMonth( startDate.getMonth() + 1 ) );
1593
1594						booking_panel_js.find("#booknetic_recurring_end").val( booknetic.convertDate( endDate.getFullYear() + '-' + booknetic.zeroPad( endDate.getMonth() + 1 ) + '-' + booknetic.zeroPad( endDate.getDate() ), 'Y-m-d' ) );
1595					}
1596				}
1597			},
1598
1599			serviceFixFrequency: function()
1600			{
1601				var serviceData = booknetic.serviceData;
1602
1603				if( serviceData && serviceData[ 'repeat_frequency' ] > 0 && serviceData[ 'repeat_type' ] === 'daily' )
1604				{
1605					booking_panel_js.find("#booknetic_daily_recurring_frequency").val( serviceData['repeat_frequency'] ).attr('disabled', true);
1606				}
1607				else
1608				{
1609					booking_panel_js.find("#booknetic_daily_recurring_frequency").attr('disabled', false);
1610				}
1611			},
1612
1613			getMonthWeekInfo: function( date, type, dayOfWeek )
1614			{
1615				var jsDate = new Date( date ),
1616					weekd = jsDate.getDay();
1617				weekd = weekd === 0 ? 7 : weekd;
1618
1619				if( weekd != dayOfWeek )
1620				{
1621					return false;
1622				}
1623
1624				const month = jsDate.getMonth() + 1,
1625					year = jsDate.getFullYear();
1626
1627				if( type === 'last' )
1628				{
1629					var nextWeek = new Date(jsDate.getTime());
1630					nextWeek.setDate( nextWeek.getDate() + 7 );
1631
1632					return nextWeek.getMonth() + 1 !== month;
1633				}
1634
1635				const firstDayOfMonth = new Date(year + '-' + booknetic.zeroPad(month) + '-01'),
1636					firstWeekDay =  firstDayOfMonth.getDay() === 0 ? 7 : firstDayOfMonth.getDay();
1637
1638				const dif = (dayOfWeek >= firstWeekDay ? dayOfWeek : parseInt(dayOfWeek) + 7) - firstWeekDay;
1639				const days = jsDate.getDate() - dif,
1640					dNumber = parseInt(days / 7) + 1;
1641
1642				return type == dNumber;
1643			},
1644
1645			confirmAppointment: async function ()
1646			{
1647				let result;
1648
1649				try {
1650					result  = await booknetic.ajaxAsync( { backend_action: 'get_data', frontend_action: 'confirm' },
1651						booknetic.ajaxParameters( {
1652							current_step: 'confirm',
1653							previous_step: booking_panel_js.find(".booknetic_appointment_step_element.booknetic_active_step").data('step-id'),
1654							info: booking_panel_js.data( 'info' )
1655						}), true
1656					);
1657				} catch (e) {
1658					booknetic.refreshGoogleReCaptchaToken();
1659					booknetic.stepManager.enableActions();
1660
1661					if (!e)
1662						return;
1663
1664					booknetic.paymentId = typeof e['payment_id'] != 'undefined' ? e['payment_id'] : null;
1665
1666					// rows are inserted before the gateway call; release the slot they still hold
1667					booknetic.releaseUnpaidAppointment( null, false );
1668
1669					if (typeof e['id'] == 'undefined')
1670						return;
1671
1672					booknetic.ajaxResultConfirmStep = e;
1673					booknetic.appointmentId = e['id'];
1674					return;
1675				}
1676
1677				booknetic.refreshGoogleReCaptchaToken();
1678
1679				booknetic.ajaxResultConfirmStep = result;
1680				booknetic.appointmentId = result['id'];
1681				booknetic.paymentId   = result['payment_id'];
1682
1683				if ( booknetic.paymentWindow !== null && result["expires_at"] !== undefined && result["expires_at"] < 24*60*60 )
1684				{
1685					let expiringWindow = booknetic.paymentWindow;
1686
1687					// a retry opens a new window; only close the one this confirm belongs to
1688					setTimeout( function ()
1689					{
1690						if ( expiringWindow === booknetic.paymentWindow && ! expiringWindow.closed )
1691							expiringWindow.close();
1692					}, result["expires_at"] * 1000 );
1693				}
1694
1695				if( booknetic.getSelected.paymentMethod() === 'local' )
1696				{
1697					booknetic.paymentFinished( true );
1698					booknetic.showFinishStep();
1699				} else if ( ! booknetic.paymentStatus ) {
1700					// inline-complete methods (e.g. gift card) already finished during confirm; don't poll and re-fire completion
1701					booknetic.startPolling(result.id)
1702				}
1703
1704				booking_panel_js.find('#booknetic_add_to_google_calendar_btn').data('url', result['google_calendar_url'] );
1705				booking_panel_js.find('#booknetic_add_to_icalendar_btn').attr('href', encodeURI( result['icalendar_url'] ) );
1706			},
1707
1708			resetPaymentState: function ()
1709			{
1710				booknetic.paymentWindow          = null;
1711				booknetic.paymentStatus          = null;
1712				booknetic.appointmentId          = null;
1713				booknetic.ajaxResultConfirmStep  = null;
1714				booknetic.paymentId              = null;
1715			},
1716
1717			releaseUnpaidAppointment: function ( onDone, loading )
1718			{
1719				onDone = typeof onDone === 'function' ? onDone : function () {};
1720
1721				if( ! booknetic.paymentId )
1722				{
1723					onDone();
1724					return;
1725				}
1726
1727				booknetic.ajax(
1728					'delete_unpaid_appointment',
1729					booknetic.ajaxParameters({ payment_id: booknetic.paymentId }),
1730					function ()
1731					{
1732						booknetic.paymentId = null;
1733						onDone();
1734					},
1735					loading !== false,
1736					// keep the id and leave the error screen up: those rows still hold the
1737					// timeslot, and on a default install nothing else ever releases them
1738					function () {}
1739				);
1740			},
1741
1742			waitPaymentFinish: function()
1743			{
1744				if( booknetic.paymentWindow.closed )
1745				{
1746					if ( booknetic.paymentStatusListener )
1747						clearInterval( booknetic.paymentStatusListener );
1748
1749					booknetic.loading(0);
1750
1751					booknetic.showFinishStep();
1752
1753					return;
1754				}
1755
1756				setTimeout( booknetic.waitPaymentFinish, 1000 );
1757			},
1758
1759			paymentFinished: function ( status )
1760			{
1761				booknetic.paymentStatus = status;
1762				booking_panel_js.find(".booknetic_appointment_finished_code").text( booknetic.zeroPad( booknetic.appointmentId, 4 ) );
1763
1764				if( booknetic.paymentWindow && !booknetic.paymentWindow.closed )
1765				{
1766					if ( booknetic.paymentStatusListener )
1767						clearInterval( booknetic.paymentStatusListener );
1768
1769					booknetic.paymentWindow.close();
1770				}
1771
1772				bookneticHooks.doAction( 'payment_completed_deprecated', booknetic );
1773				booknetic.stopPolling()
1774			},
1775
1776			showFinishStep: function ()
1777			{
1778				if ( BookneticData.settings.redirect_users_on_confirm === true && booknetic.paymentStatus === true )
1779				{
1780					window.location.href = BookneticData.settings.redirect_users_on_confirm_url;
1781					return;
1782				}
1783
1784				if( booknetic.paymentStatus === true )
1785				{
1786					booking_panel_js.find('.booknetic_appointment_container').fadeOut(95);
1787
1788					if ( booking_panel_js.find('.booknetic_appointment_steps').css( 'display' ) === 'none' )
1789					{
1790						booking_panel_js.find('.booknetic_appointment_finished').fadeIn(100).css('display', 'flex');
1791					} else {
1792						booking_panel_js.find('.booknetic_appointment_steps').fadeOut(100, function ()
1793						{
1794							booking_panel_js.find('.booknetic_appointment_finished').fadeIn(100).css('display', 'flex');
1795						});
1796					}
1797
1798					bookneticHooks.doAction( 'booking_finished_successfully', booknetic );
1799				}
1800				else
1801				{
1802					booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="confirm_details"]').fadeOut( 150, function()
1803					{
1804						booking_panel_js.find('.booknetic_appointment_container_body > .booknetic_appointment_finished_with_error').removeClass('booknetic_hidden').hide().fadeIn( 150 );
1805					});
1806
1807					booking_panel_js.find('.booknetic_next_step.booknetic_confirm_booking_btn').fadeOut( 150, function()
1808					{
1809						booking_panel_js.find('.booknetic_try_again_btn').removeClass('booknetic_hidden').hide().fadeIn( 150 );
1810					});
1811
1812					booking_panel_js.find('.booknetic_appointment_container_header_cart').fadeOut( 150 );
1813
1814					booking_panel_js.find('.booknetic_prev_step').css('opacity', '0').attr('disabled', true);
1815
1816					bookneticHooks.doAction( 'payment_error', booknetic );
1817				}
1818
1819				if ( booknetic.isMobileView() )
1820				{
1821					$('html,body').animate({scrollTop: parseInt(booking_panel_js.offset().top) - 100}, 1000);
1822				}
1823			},
1824
1825			fadeInAnimate: function(el, fn)
1826			{
1827				let sec = 150;
1828				let delay = 50;
1829				let count = booking_panel_js.find(el).length;
1830
1831				if( booking_panel_js.find(el).length === 0 )
1832					fn();
1833
1834				booking_panel_js.find(el).hide().each(function (i)
1835				{
1836					(function( i, t, isLast, fn )
1837					{
1838						setTimeout( function ()
1839						{
1840							t.fadeIn( (i > 6 ? 6 : i) * sec, ( isLast ? fn : null ));
1841						}, (i > 6 ? 6 : i) * delay );
1842					})( i, $(this), (count-1===i), fn );
1843				});
1844			},
1845
1846			fadeOutAnimate: function(el, sec, delay)
1847			{
1848				sec = sec > 0 ? sec : 150;
1849				delay = delay > 0 ? delay : 50;
1850
1851				booking_panel_js.find(el).each(function (i)
1852				{
1853					(function( i, t )
1854					{
1855						setTimeout( function ()
1856						{
1857							t.fadeOut( (i > 6 ? 6 : i) * sec );
1858						}, (i > 6 ? 6 : i) * delay );
1859					})( i, $(this) );
1860				});
1861			},
1862
1863
1864			_bookneticScroll: false,
1865			handleScroll: function ()
1866			{
1867				if( !booknetic._bookneticScroll && !booknetic.isMobileView() )
1868				{
1869					booking_panel_js.find(".booknetic_appointment_container_body").addClass('nice-scrollbar-primary');
1870
1871					booknetic._bookneticScroll = true;
1872
1873					return;
1874				}
1875
1876				if( booknetic.isMobileView() && booknetic._bookneticScroll )
1877				{
1878					booknetic._bookneticScroll = false;
1879
1880					booking_panel_js.find(".booknetic_appointment_container_body").removeClass('nice-scrollbar-primary');
1881
1882					if ( $( '#country-listbox' ).length )
1883					{
1884						$( '#country-listbox' ).removeClass('nice-scrollbar-primary');
1885					}
1886				}
1887			},
1888
1889			getDateWithUTC: function ( date )//if the client timezone is negative
1890			{
1891				date = new Date( date );
1892				let offset  = date.getTimezoneOffset();
1893
1894				if ( offset > 0 ) // if offset a positive number, client's timezone is negative
1895					date.setTime( date.getTime() + ( offset * 60 * 1000 ) ); //get UTC time
1896
1897				return date;
1898			},
1899
1900			initDatepicker: function ( el )
1901			{
1902				// Map our display tokens (yyyy/mm/dd) to flatpickr's (Y/m/d).
1903				var fpFormat = booknetic.datePickerFormat()
1904					.replace('yyyy', 'Y')
1905					.replace('mm', 'm')
1906					.replace('dd', 'd');
1907
1908				var minDate;
1909				if ( el[0].getAttribute('data-apply-min') && el[0].value )
1910				{
1911					minDate = booknetic.convertDate( el[0].value, booknetic.datePickerFormat(), 'Y-m-d' );
1912				}
1913
1914				flatpickr( el[0], {
1915					dateFormat: fpFormat,
1916					allowInput: true,
1917					locale: {
1918						firstDayOfWeek: BookneticData.week_starts_on == 'sunday' ? 0 : 1,
1919						weekdays: {
1920							shorthand: [__('Sun'), __('Mon'), __('Tue'), __('Wed'), __('Thu'), __('Fri'), __('Sat')],
1921							longhand:  [__('Sunday'), __('Monday'), __('Tuesday'), __('Wednesday'), __('Thursday'), __('Friday'), __('Saturday')]
1922						},
1923						months: {
1924							shorthand: [__('Jan'), __('Feb'), __('Mar'), __('Apr'), __('May'), __('Jun'), __('Jul'), __('Aug'), __('Sep'), __('Oct'), __('Nov'), __('Dec')],
1925							longhand:  [__('January'), __('February'), __('March'), __('April'), __('May'), __('June'), __('July'), __('August'), __('September'), __('October'), __('November'), __('December')]
1926						}
1927					},
1928					minDate: minDate,
1929					onChange: function ( selectedDates, dateStr, instance )
1930					{
1931						$(instance.input).trigger('change');
1932					}
1933				});
1934			},
1935
1936			refreshGoogleReCaptchaToken: function ()
1937			{
1938				if( 'google_recaptcha_site_key' in BookneticData )
1939				{
1940					grecaptcha.execute( BookneticData['google_recaptcha_site_key'], { action: booknetic.google_recaptcha_action }).then(function (token)
1941					{
1942						booknetic.google_recaptcha_token = token;
1943					});
1944				}
1945			},
1946
1947			isMobileView: function ()
1948			{
1949				return window.matchMedia('(max-width: 1000px)').matches;
1950			},
1951
1952			stepManager: {
1953
1954				stepValidation: function ( step )
1955				{
1956					let result = bookneticHooks.doFilter( `step_validation_${step}`, {
1957						status: true,
1958						errorMsg: ''
1959					}, booknetic );
1960
1961					if( result.status )
1962						bookneticHooks.doAction(`step_end_${step}` , booknetic);
1963
1964					return result;
1965				},
1966
1967				loadStep: function( step )
1968				{
1969					if( ! bookneticHooks.doFilter( `load_step_${step}` , booknetic ) )
1970						return false;
1971
1972					var current_step_el	= booking_panel_js.find('.booknetic_appointment_step_element.booknetic_active_step');
1973					var next_step_el	= booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="'+step+'"]');
1974
1975					while( next_step_el.hasClass('booknetic_menu_hidden') )
1976						next_step_el = next_step_el.next();
1977
1978					booknetic.stepManager.disableAction();
1979
1980					var next_step_id    = next_step_el.data('step-id');
1981					var current_step_id = '';
1982
1983					if( current_step_el.length > 0 )
1984					{
1985						current_step_el.removeClass('booknetic_active_step');
1986						current_step_id	= current_step_el.data('step-id');
1987						booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="' + current_step_id + '"]').hide();
1988					}
1989
1990					next_step_el.addClass('booknetic_active_step');
1991					booking_panel_js.find(".booknetic_appointment_container_header_text").text( next_step_el.data('title') );
1992
1993					booknetic.stepManager.updateGoNextBtn( next_step_el );
1994
1995					bookneticHooks.doAction('before_step_loading', booknetic, next_step_id, current_step_id);
1996
1997					booknetic.stepManager.saveData();
1998
1999					booknetic.stepManager.updateBookingPanelFooter();
2000
2001					return true;
2002				},
2003
2004				loadStandartSteps: function ( next_step_id, current_step_id )
2005				{
2006					if( ! booknetic.stepManager.needToReloadCache( next_step_id ) )
2007					{
2008						booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="' + next_step_id + '"]').show();
2009
2010						booknetic.fadeInAnimate('.booknetic_appointment_container_body [data-step-id="' + next_step_id + '"] .booknetic_fade', function()
2011						{
2012							booking_panel_js.find(".booknetic_appointment_container_body").scrollTop(0);
2013							booknetic.handleScroll();
2014							booknetic.stepManager.enableActions();
2015						});
2016						bookneticHooks.doAction('loaded_cached_step', booknetic, next_step_id);
2017					}
2018					else
2019					{
2020						var next_step_el	= booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="'+next_step_id+'"]');
2021						var loader  = booking_panel_js.find('.booknetic_preloader_' + next_step_el.data('loader') + '_box');
2022
2023						booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="' + next_step_id + '"]').empty();
2024
2025						bookneticHooks.doAction('start_step_loading', booknetic, next_step_id, current_step_id);
2026
2027						loader.removeClass('booknetic_hidden').show();
2028						let step
2029						const bookingPanelParams = {
2030							current_step: next_step_id,
2031							previous_step: booknetic.stepManager.getPrevStep().data( 'step-id' ),
2032							info: booking_panel_js.data( 'info' ),
2033						}
2034						if (next_step_id === "location" && (step = booking_panel_js.data('steps').find(s => s.id === "location"))?.options) {
2035							bookingPanelParams.location_filter = JSON.stringify(step.options);
2036						}
2037
2038						booknetic.ajax( 'get_data', booknetic.ajaxParameters({...bookingPanelParams}), function ( result )
2039						{
2040							loader.hide();
2041							booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="' + next_step_id + '"]').show().html( booknetic.htmlspecialchars_decode( booknetic.sanitizeHTML(result['html']) ) );
2042
2043							booknetic.fadeInAnimate('.booknetic_appointment_container_body [data-step-id="' + next_step_id + '"] .booknetic_fade', function ()
2044							{
2045								booking_panel_js.find(".booknetic_appointment_container_body").scrollTop(0);
2046								booknetic.handleScroll();
2047							});
2048
2049							booknetic.stepManager.enableActions();
2050
2051							bookneticHooks.doAction( 'loaded_step', booknetic, next_step_id, current_step_id, result );
2052							bookneticHooks.doAction( `loaded_step_${next_step_id}`, booknetic, result );
2053
2054						}, false , function ( result )
2055						{
2056							loader.hide();
2057							booknetic.stepManager.goBack();
2058							bookneticHooks.doAction('step_loaded_with_error', booknetic, next_step_id, current_step_id, result);
2059						});
2060					}
2061				},
2062
2063				disableAction: function ()
2064				{
2065					booking_panel_js.find(".booknetic_next_step, .booknetic_prev_step").attr('disabled', true);
2066				},
2067
2068				enableActions: function ()
2069				{
2070					booking_panel_js.find(".booknetic_next_step, .booknetic_prev_step").attr('disabled', false);
2071				},
2072
2073				updateBookingPanelFooter: function ()
2074				{
2075					booknetic.stepManager.updateGoBackButton();
2076				},
2077
2078				updateGoNextBtn: function ( step_el )
2079				{
2080					var next_step_el	= step_el.next('.booknetic_appointment_step_element');
2081
2082					while( next_step_el.hasClass('booknetic_menu_hidden') )
2083						next_step_el = next_step_el.next();
2084
2085					if ( next_step_el.length === 0 ) {
2086						booking_panel_js.find('.booknetic_confirm_booking_btn').show();
2087						booking_panel_js.find('.booknetic_next_step_btn').hide();
2088					}
2089					else
2090					{
2091						booking_panel_js.find('.booknetic_confirm_booking_btn').hide();
2092						booking_panel_js.find('.booknetic_next_step_btn').show();
2093					}
2094				},
2095
2096				updateGoBackButton: function ()
2097				{
2098					const stepElement = '.booknetic_appointment_step_element';
2099					const activeElement = '.booknetic_active_step';
2100					const menuHidden = '.booknetic_menu_hidden';
2101
2102					// hide the BACK button for the first step
2103					if (  booking_panel_js.find(stepElement ).not( menuHidden ).first().is( booking_panel_js.find( activeElement ) ) ) {
2104						booking_panel_js.find( '.booknetic_prev_step' ).css( 'opacity', 0 ).css( 'pointer-events', 'n
2104one' );
2105						return;
2106					}
2107
2108					booking_panel_js.find( '.booknetic_prev_step' ).css( 'opacity', 1 ).css( 'pointer-events', 'auto' );
2109				},
2110
2111				saveData: ()=>
2112				{
2113					let obj = {};
2114
2115					if(booknetic.cartArr[ booknetic.cartCurrentIndex ] !== undefined)
2116					{
2117						obj = booknetic.cartArr[ booknetic.cartCurrentIndex ];
2118					}
2119
2120					obj['location'] =  booknetic.getSelected.location();
2121					obj['location_category'] =  booknetic.getSelected.locationCategory();
2122					obj['staff'] =  booknetic.getSelected.staff();
2123					obj['service_category'] =  booknetic.getSelected.serviceCategory();
2124					obj['service'] =  booknetic.getSelected.service();
2125					obj['service_extras'] =  booknetic.getSelected.serviceExtras();
2126
2127					obj['date'] =  booknetic.getSelected.date();
2128					obj['time'] =  booknetic.getSelected.time();
2129					obj['brought_people_count'] =  booknetic.getSelected.brought_people_count();
2130
2131					obj['recurring_start_date'] =  booknetic.getSelected.recurringStartDate();
2132					obj['recurring_end_date'] =  booknetic.getSelected.recurringEndDate();
2133					obj['recurring_times'] =  booknetic.getSelected.recurringTimesArr();
2134					obj['appointments'] =  booknetic.getSelected.recurringTimesArrFinish();
2135
2136					obj['customer_id'] = booknetic.getSelected.customerId();
2137					obj['customer_data'] = booknetic.getSelected.formData()['data'];
2138
2139					booknetic.cartArr[ booknetic.cartCurrentIndex ] = bookneticHooks.doFilter('bkntc_cart' , obj , booknetic );
2140				},
2141
2142				goForward: function ()
2143				{
2144					let current_step_el	= booking_panel_js.find(".booknetic_appointment_step_element.booknetic_active_step"),
2145						current_step_id = current_step_el.data('step-id'),
2146						next_step_el	= booknetic.stepManager.getNextStep(),
2147						next_step_id    = next_step_el.data('step-id'),
2148						validate_step   = booknetic.stepManager.stepValidation( current_step_id );
2149
2150					if( validate_step.status == false )
2151					{
2152						booknetic.toast( validate_step.errorMsg );
2153						return;
2154					}
2155
2156					if( next_step_el.length == 0 )
2157					{
2158						booknetic.stepManager.disableAction();
2159						booknetic.confirmAppointment();
2160						return;
2161					}
2162
2163					current_step_el.addClass('booknetic_selected_step');
2164
2165					let oldData = JSON.stringify( booknetic.cartArr[ booknetic.cartCurrentIndex ] );
2166					booknetic.stepManager.saveData();
2167					let newData = JSON.stringify( booknetic.cartArr[ booknetic.cartCurrentIndex ] );
2168
2169					if( oldData !== newData )
2170					{
2171						let startToEmpty = next_step_el;
2172						while( startToEmpty.length > 0 )
2173						{
2174							booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="'+startToEmpty.data('step-id')+'"]').empty();
2175							startToEmpty = startToEmpty.next();
2176						}
2177					}
2178
2179					booknetic.toast( false );
2180
2181					if( booknetic.stepManager.loadStep( next_step_id ) && booknetic.isMobileView() )
2182					{
2183						const modalBody = booknetic.panel_js.closest('.bkntc_booking_modal').find('.content .body');
2184
2185						if (modalBody.length > 0) {
2186							modalBody.animate({scrollTop: 0}, 500);
2187							return;
2188						}
2189
2190						$('html,body').animate({scrollTop: parseInt(booknetic.panel_js.offset().top) - 100}, 1000);
2191					}
2192				},
2193
2194				goBack: function ()
2195				{
2196					let current_step_el	= booking_panel_js.find(".booknetic_appointment_step_element.booknetic_active_step"),
2197						prev_step_el    = booknetic.stepManager.getPrevStep();
2198
2199					if (current_step_el.data('step-id') === "date_time") {
2200						booknetic.current_month_is_empty = undefined
2201					}
2202
2203					current_step_el.removeClass('booknetic_selected_step').nextAll('.booknetic_appointment_step_element').removeClass('booknetic_selected_step');
2204
2205					if( prev_step_el.length > 0 )
2206					{
2207						if (prev_step_el.data( 'step-id' ) === 'service_extras' && BookneticData[ 'skip_extras_step_if_need' ] === 'on' && prev_step_el.css('display') === 'none')
2208						{
2209							prev_step_el.css('display', 'block');
2210							prev_step_el.removeClass('booknetic_selected_step');
2211
2212							do{
2213								prev_step_el = prev_step_el.prev();
2214							}
2215							while(prev_step_el.hasClass('booknetic_menu_hidden'));
2216						}
2217
2218						current_step_el.removeClass('booknetic_active_step');
2219						prev_step_el.addClass('booknetic_active_step');
2220
2221						booknetic.stepManager.disableAction();
2222						booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="' + current_step_el.data('step-id') + '"]').fadeOut(200, function()
2223						{
2224							booknetic.stepManager.enableActions();
2225							booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="' + prev_step_el.data('step-id') + '"]').fadeIn(200, function ()
2226							{
2227								booknetic.handleScroll();
2228							});
2229						});
2230
2231						booking_panel_js.find(".booknetic_appointment_container_header_text").text( prev_step_el.data('title') );
2232					}
2233
2234					booking_panel_js.find('.booknetic_confirm_booking_btn').hide();
2235					booking_panel_js.find('.booknetic_next_step_btn').show();
2236
2237					booknetic.stepManager.updateBookingPanelFooter();
2238				},
2239
2240				getNextStep: function ()
2241				{
2242					let current_step_el	= booking_panel_js.find(".booknetic_appointment_step_element.booknetic_active_step"),
2243						next_step_el	= current_step_el.next('.booknetic_appointment_step_element');
2244
2245					while( next_step_el.hasClass('booknetic_menu_hidden') )
2246						next_step_el = next_step_el.next();
2247
2248					return next_step_el;
2249				},
2250
2251				getPrevStep: function ()
2252				{
2253					if( booknetic.cartPrevStep != undefined)
2254					{
2255						let x = booknetic.cartPrevStep;
2256						booknetic.cartPrevStep = undefined;
2257						return booking_panel_js.find(".booknetic_appointment_steps_body div[data-step-id=" + x + "]");
2258					}
2259					let current_step_el	= booking_panel_js.find(".booknetic_appointment_step_element.booknetic_active_step"),
2260						prev_step_el    = current_step_el.prev('.booknetic_appointment_step_element');
2261
2262					while( prev_step_el.hasClass('booknetic_menu_hidden') )
2263						prev_step_el = prev_step_el.prev();
2264
2265					return prev_step_el;
2266				},
2267
2268				getCurrentStep: function ()
2269				{
2270					return booking_panel_js.find(".booknetic_appointment_step_element.booknetic_active_step");
2271				},
2272
2273				refreshStepNumbers: function ()
2274				{
2275					var index = 1;
2276
2277					booking_panel_js.find('.booknetic_appointment_steps_body > .booknetic_appointment_step_element').each(function()
2278					{
2279						if( $(this).css('display') != 'none' )
2280						{
2281							$(this).find('.booknetic_badge').text( index );
2282							index++;
2283						}
2284					});
2285				},
2286
2287				needToReloadCache: function( stepId )
2288				{
2289					if( stepId == 'confirm_details' || stepId=='cart' )
2290						return true;
2291
2292					if( booking_panel_js.find('.booknetic_appointment_container_body [data-step-id="' + stepId + '"] > *').length > 0 )
2293					{
2294						return false;
2295					}
2296
2297					return true;
2298				},
2299
2300			},
2301
2302			addGroupAppointmentsCounterForBookneticCalendarDays: function ()
2303			{
2304				const dates = booknetic.calendarDateTimes['dates'];
2305
2306				for ( const date in dates )
2307				{
2308					if ( dates[ date ].length !== 1 )
2309					{
2310						continue;
2311					}
2312
2313					let max_capacity = dates[ date ][ 0 ].max_capacity;
2314					let weight = dates[ date ][ 0 ].weight;
2315
2316					if (
2317						weight == 0 ||
2318						'hide_available_slots' in booknetic.calendarDateTimes &&
2319						booknetic.calendarDateTimes['hide_available_slots'] == 'on'
2320					)
2321					{
2322						continue;
2323					}
2324
2325					booking_panel_js.find( `.booknetic_calendar_days[data-date=${date}] > div` ).append( `<div class="booknetic_time_group_num booknetic_date_group_num">${weight} / ${max_capacity}</div>` );
2326				}
2327			},
2328
2329			checkIfNoDatesAvailable: function ( backendResult )
2330			{
2331				return backendResult[ 'data' ][ 'dates' ].length === 0 || Object.values( backendResult[ 'data' ][ 'dates' ] ).every( d => d.length === 0 );
2332			},
2333
2334			intervalId: 0,
2335
2336            startPolling: (appointmentId) => {
2337                // Clear any existing polling
2338                if (booknetic.intervalId) clearInterval(booknetic.intervalId);
2339
2340                const intervalId = setInterval(() => {
2341                    booknetic.ajaxAsync('isPaymentDone', { id: appointmentId }, false)
2342                        .then(result => {
2343                            if (!result.isDone) return;
2344                            clearInterval(intervalId);
2345                            booknetic.paymentFinished(true);
2346                        })
2347                        .catch(_ => clearInterval(intervalId));
2348                }, 3000);
2349
2350                setTimeout(() => clearInterval(intervalId), 1000 * 60 * 10);
2351
2352                booknetic.intervalId = intervalId;
2353            },
2354
2355
2356            stopPolling: () => {
2357                if (!booknetic.intervalId) return;
2358
2359                clearInterval(booknetic.intervalId);
2360                booknetic.intervalId = 0;
2361            },
2362		};
2363
2364		// steplerle bagli basic eventler
2365		booking_panel_js.on('click', '.booknetic_next_step', function()
2366		{
2367			booknetic.stepManager.goForward();
2368		}).on('click', '.booknetic_prev_step', function()
2369		{
2370			booknetic.stepManager.goBack();
2371		});
2372
2373		booking_panel_js.on('click', '#booknetic_finish_btn', function ()
2374		{
2375			let page = window.location;
2376
2377			// check if iframe
2378			if ( window.location !== window.parent.location )
2379			{
2380				page = window.parent.location;
2381			}
2382
2383			if( ! $(this).data('redirect-url') )
2384			{
2385				page.reload();
2386			}
2387			else
2388			{
2389				page.href = $(this).data('redirect-url');
2390			}
2391		}).on('click' ,'.bkntc_again_booking' , function ()
2392		{
2393			let currentBody = booking_panel_js.find('.booknetic_need_copy');
2394			booknetic.cartHTMLBody[booknetic.cartCurrentIndex] = currentBody.clone(true,true).get(0);
2395
2396			booking_panel_js.find('.booknetic_appointment_steps_body .booknetic_appointment_step_element.need_copy').each(function()
2397			{
2398				let id = $(this).attr('data-step-id');
2399				if(booknetic.cartHTMLSideBar[booknetic.cartCurrentIndex] == undefined)
2400				{
2401					booknetic.cartHTMLSideBar[booknetic.cartCurrentIndex] = {};
2402				}
2403				booknetic.cartHTMLSideBar[booknetic.cartCurrentIndex][id] = $(this).clone(true,true).get(0);
2404			});
2405
2406			booknetic.cartCurrentIndex = booknetic.cartArr.length;
2407			booknetic.cartArr[booknetic.cartCurrentIndex] = {};
2408
2409			booking_panel_js.find("#booknetic_start_new_booking_btn").trigger('click' , true);
2410
2411		}).on('click', '#booknetic_start_new_booking_btn', function ( e , param )
2412		{
2413			if( param === undefined )
2414			{
2415				booking_panel_js.find('.booknetic_appointment_container_header_cart').fadeOut();
2416
2417				booknetic.cartHTMLBody 	   = [];
2418				booknetic.cartHTMLSideBar  = [];
2419				booknetic.cartArr 		   = [];
2420				booknetic.cartCurrentIndex = 0;
2421			}
2422			booking_panel_js.find('.booknetic_appointment_finished').fadeOut(100, function()
2423			{
2424				const appointment_step_style = booking_panel_js.find('.booknetic_appointment_steps').attr( 'style' );
2425
2426				// in case if booking_panel_js.find('.booknetic_appointment_steps').fadeOut() called
2427				if ( appointment_step_style && appointment_step_style.search( 'display: none;' ) !== -1 )
2428					booking_panel_js.find('.booknetic_appointment_steps').fadeIn(100);
2429
2430				booking_panel_js.find('.booknetic_appointment_container').fadeIn(100);
2431			});
2432
2433			booking_panel_js.find(".booknetic_selected_step").removeClass('booknetic_selected_step');
2434			booking_panel_js.find(".booknetic_active_step").removeClass('booknetic_active_step');
2435
2436			booknetic.current_month_is_empty 	= undefined
2437			booknetic.calendarDateTimes			= {};
2438			booknetic.time_show_format			= 1;
2439			booknetic.calendarYear				= null;
2440			booknetic.calendarMonth				= null;
2441			booknetic.resetPaymentState();
2442
2443			var start_step = booking_panel_js.find(".booknetic_appointment_step_element:not(.booknetic_menu_hidden):eq(0)");
2444			start_step.addClass('booknetic_active_step');
2445			booking_panel_js.find('.booknetic_appointment_container_body  [data-step-id] ').empty();
2446			booknetic.stepManager.loadStep( start_step.data('step-id') );
2447
2448			booking_panel_js.find('.booknetic_appointment_container_body  [data-step-id]').hide();
2449			booking_panel_js.find('.booknetic_appointment_container_body  [data-step-id="' + start_step.data('step-id') + '"]').show();
2450
2451			booking_panel_js.find('.booknetic_card_selected').removeClass('booknetic_card_selected');
2452			booking_panel_js.find('.booknetic_service_card_selected').removeClass('booknetic_service_card_selected');
2453			booking_panel_js.find('.booknetic_service_card_selected').removeClass('booknetic_service_card_selected');
2454
2455			booking_panel_js.find(".booknetic_calendar_selected_day").data('date', null);
2456			booking_panel_js.find(".booknetic_selected_time").data('time', null);
2457
2458			booknetic.handleScroll();
2459
2460		}).on('click', '#booknetic_add_to_google_calendar_btn', function ()
2461		{
2462			window.open( $(this).data('url') );
2463		}).on('click', '.booknetic_try_again_btn', function ()
2464		{
2465			booknetic.stopPolling();
2466
2467			let restoreConfirmStep = function ()
2468			{
2469				booknetic.resetPaymentState();
2470
2471				booknetic.stepManager.enableActions();
2472
2473				booking_panel_js.find('.booknetic_appointment_finished_with_error').fadeOut(150, function ()
2474				{
2475					booking_panel_js.find('.booknetic_appointment_container_body  [data-step-id="confirm_details"]').fadeIn(150, function ()
2476					{
2477						booknetic.handleScroll();
2478					});
2479				});
2480
2481				booking_panel_js.find('.booknetic_try_again_btn').fadeOut(150, function ()
2482				{
2483					booking_panel_js.find('.booknetic_next_step.booknetic_confirm_booking_btn').fadeIn(150);
2484					booking_panel_js.find('.booknetic_prev_step').css('opacity', '1');
2485				});
2486
2487				if( ! booking_panel_js.find('.booknetic_appointment_step_element[data-step-id="cart"]').hasClass('booknetic_menu_hidden') )
2488				{
2489					booking_panel_js.find('.booknetic_appointment_container_header_cart').fadeIn( 150 );
2490				}
2491			};
2492
2493			booknetic.releaseUnpaidAppointment( restoreConfirmStep );
2494		});
2495
2496		//doit staff stepi (locationdan ayirmag)
2497
2498		$( window ).resize(function ()
2499		{
2500			booknetic.handleScroll();
2501		});
2502
2503		let bookneticStepsArray = booking_panel_js.data('steps');
2504		for( let step of bookneticStepsArray )
2505		{
2506			let classAttr = 'booknetic_appointment_step_element';
2507			if( step.id === 'cart' || step.id === 'confirm_details' )
2508				classAttr += ' need_copy';
2509
2510			if( step.hidden )
2511				classAttr += ' booknetic_menu_hidden';
2512
2513			let attrs = step.attrs ?? '';
2514
2515			attrs += ` data-step-id="${step.id}"`;
2516			attrs += ` data-loader="${step.loader}"`;
2517			attrs += ` data-title="${step.head_title}"`;
2518
2519			if( step.value != "" )
2520				attrs += ` data-value="${step.value}"`;
2521
2522			booking_panel_js.find('.booknetic_appointment_steps_body').append(`
2523					<div class="${classAttr}" ${attrs}>
2524	                    <span class="booknetic_badge"></span>
2525	                    <span class="booknetic_step_title"> ${step.title}</span>
2526	                </div>
2527				`);
2528		}
2529
2530		var first_step_id = booking_panel_js.find('.booknetic_appointment_steps_body > .booknetic_appointment_step_element:not(.booknetic_menu_hidden)').eq(0).data('step-id');
2531		booknetic.ensureFreshNonce( function ()
2532		{
2533			booknetic.stepManager.loadStep( first_step_id );
2534		} );
2535
2536		booknetic.handleScroll();
2537
2538		booknetic.fadeInAnimate('.booknetic_appointment_step_element:not(.booknetic_menu_hidden)', function ()
2539		{
2540			booknetic.stepManager.refreshStepNumbers();
2541		});
2542
2543		booking_panel_js.find(".booknetic_appointment_steps_footer").fadeIn(200);
2544
2545		if( 'google_recaptcha_site_key' in BookneticData )
2546		{
2547			grecaptcha.ready(function ()
2548			{
2549				booknetic.refreshGoogleReCaptchaToken();
2550			});
2551		}
2552
2553		bookneticHooks.doAction('booking_panel_loaded', booknetic);
2554
2555		return booknetic;
2556	};
2557
2558	$(document).ready( function()
2559	{
2560		if ( $( 'html' ).attr( 'dir' ) === 'rtl' )
2561		{
2562			$( 'body' ).addClass( 'rtl booknetic_rtl' );
2563		}
2564
2565		/**
2566		 * doit bu duzelmelidi. hem alqoritmde xeta var, meselen Cart stepi load olanda headerda icon show olur. geri qayidib nese deyishiklik eledik meselen Servisi deyishdik.
2567		 * data deyishir deye cart iconu sifirlanmalidi. chunki butun stepleri tamamlamalidi, date&time sechmelidi, formu tezeden doldurmalidi, chunki service deyishende form inputlarda deyishe biler.
2568		 * Amma bu stepleri doldurmadan hemen show olmush icona clickledikde birbasha tulluyur cart stepine. Ordanda next bassan error verir ki, meselen formu doldur ve ya date&time sech ve s.
2569		 * Elave olarag $('.*') kimi select yolverilmezdir. booknetic.panel_js.find('.*') || booking_panel_js.find('.*') istifade olunmalidir.
2570		 * Cunki 1 pagede eyni anda bir neche booking panel load ola biler. biri digerinin iconlariynan oynamasin deye bu metod hokmen her yerde istifade edilmelidi.
2571		 */
2572		$("body").click(function(e)
2573		{
2574			if( $(e.target).parent().hasClass('booknetic-cart-item-more') )
2575			{
2576				let a = $(e.target).parents('.booknetic-cart-item-header').find('.booknetic-cart-item-btns').first();
2577				let b = a.hasClass('show');
2578				$(".booknetic-cart-item-btns").removeClass("show");
2579				if(!b)
2580				{
2581					a.addClass('show')
2582				}
2583				else
2584				{
2585					a.removeClass('show');
2586				}
2587			}
2588			else
2589			{
2590
2591				$(".booknetic-cart-item-btns").removeClass("show");
2592
2593			}
2594		});
2595	});
2596
2597})(jQuery);
2598
2599// local payment gateway
2600(function($)
2601{
2602	"use strict";
2603
2604	$(document).ready(function()
2605	{
2606		let paymentData;
2607
2608		bookneticHooks.addAction( 'before_processing_payment', function ( payment_method, data )
2609		{
2610			if( payment_method !== 'local' )
2611				return;
2612
2613			paymentData = data;
2614		});
2615
2616		bookneticHooks.addAction( 'after_processing_payment', function( payment_method, process_status, data )
2617		{
2618			if( payment_method !== 'local' )
2619				return;
2620
2621			if( ! process_status )
2622			{
2623				return;
2624			}
2625
2626			bookneticHooks.doAction('payment_completed', true, paymentData);
2627		});
2628	});
2629
2630})(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.