PageSourceSearch

https://lancashirehipkneeclinic.co.uk/wp-content/plugins/booknetic…/assets/js/booknetic.js?v=1.2.3&ver=1.2.3

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