PageSourceSearch

https://guidogobino.com/wp-content/plugins/booknetic/app/Frontend/…=5.0.8&ver=14709fc6ea8db5c88d99b1fa8244e4f8

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

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.