PageSourceSearch

https://jokai.interticket.sk/resources/js/auditview/HTML5Audit.js?v=1790238615

js interticket.sk collected 2026-09-24 23:46:42 UTC 137,585 bytes, 4,580 lines download raw bytes

1$(function() {
2	//price group toggle events
3	$(document).on('mouseenter', 'div.ticket-categories > ul > li', function() {
4		TicketGroup.select($(this).data('group'));
5	});
6	$(document).on('mouseleave', 'div.ticket-categories > ul > li', function() {
7		TicketGroup.unselect($(this).data('group'));
8	});
9	$(document).on('click', 'div.ticket-categories > ul > li', function() {
10		TicketGroup.toggleHoldSelection($(this).data('group'));
11	});
12	//end of: price group toggle
13	
14	$(document).on('click', '#' + Consts.domIds.tooltip + ' a[href="#"]', function() {
15		return false;
16	});
17	
18	//tap event for mobile
19	if (typeof Config.mobile_fast_selection !== 'undefined' && Config.mobile_fast_selection) {
20		//mobile use fast seat selection: one click (without tooltip)
21		Seat.mobileFastSelection();
22	} else {
23		//fix tooltip hiding on mobile double tap mode
24		$('div#audit').on('click touchstart', function() {
25			setTimeout(function() {
26				if (!audit.seatTooltip.showinit) {
27					audit.seatTooltip.hide();
28					setTimeout(function() {
29						if (!$('#'+Consts.domIds.tooltip).is(':visible')) {
30							document.dispatchEvent(new CustomEvent("clearHighlight"));
31						}
32					}, 300);
33				}
34			}, 200);
35		});
36	}
37	//end of: tap
38	
39	//minimap enable/disable
40	MiniMAP.init();
41	
42	//testing
43	if (window.location.hash === '#test') {
44		Testing.start();
45	}
46	
47	if (window.location.hash === '#dev') {
48		Seat.collectMode = true;
49	}
50	$(document).on('click', '.pw-eye-btn', function() {
51		if ($(this).find('.fa').hasClass('fa-eye-slash')) {
52			$(this).find('.fa').removeClass('fa-eye-slash');
53			$(this).find('.fa').addClass('fa-eye');
54			$(this).closest('.input-group').find('.pw-eye-fld').attr('type', 'text');
55		} else {
56			$(this).find('.fa').removeClass('fa-eye');
57			$(this).find('.fa').addClass('fa-eye-slash');
58			$(this).closest('.input-group').find('.pw-eye-fld').attr('type', 'password');
59		}
60	});
61	$('#buttonBlind').click(function() {
62		document.location.search = '?force_arrival=1';
63	});
64});
65
66var Consts = {
67	statusIds: {
68		empty: 0,
69		selected: 1,
70		occupied: 2
71	},
72	
73	domIds: {
74		loadingLabel: 'loading',
75		audit: 'audit',
76		sectorsLayer: 'Bsectors',
77		seatsLayer: 'Bseats',
78		tooltip: 'tooltip',
79		capacityPopup: 'capacityPopup',
80		
81		btnZoomIn: 'buttonPlus',
82		btnZoomOut: 'buttonMinus',
83		btnShowSectors: 'buttonSectors',
84		btnReset: 'buttonEye'
85	}
86};
87
88function auditBasketManager() {
89	this.ticketCount = 0;
90	this.sumPrice = 0;
91	this.ticketPrices = 0;
92	this.serviceCharge = 0;
93	this.collected = false;
94	
95	this.addItem = function(quantity, price, ticketprice) {
96		quantity = parseInt(quantity, 10);
97		price = parseFloat(price);
98		ticketprice = typeof ticketprice === 'undefined' ? false : parseFloat(ticketprice);
99		var sc = ticketprice === false
100			? (this.getServiceCharge(quantity, price) * quantity)
101			: Math.max(0, price - ticketprice);
102			
103		this.ticketCount += quantity;
104		this.serviceCharge += sc;
105		if (ticketprice === false) {
106			this.ticketPrices += price;
107			this.sumPrice += price + sc;
108		} else {
109			this.ticketPrices += ticketprice;
110			this.sumPrice += price;
111		}
112		
113		this.updateDisplay();
114	};
115	
116	this.removeItem = function(quantity, price) {
117		var sc = this.getServiceCharge(quantity, price);
118		price = parseFloat(price);
119		
120		this.ticketCount -= parseInt(quantity, 10);
121		if (this.ticketCount < 0) {
122			this.ticketCount = 0;
123		}
124		
125		this.ticketPrices -= price;
126		if (this.ticketCount === 0 || this.ticketPrices < 0) {
127			this.ticketPrices = 0;
128		}
129		
130		this.serviceCharge -= sc;
131		if (this.ticketCount === 0 || this.serviceCharge < 0) {
132			this.serviceCharge = 0;
133		}
134		
135		this.sumPrice -= price + sc;
136		if (this.ticketCount === 0 || this.sumPrice < 0) {
137			this.sumPrice = 0;
138		}
139		
140		this.updateDisplay();
141	};
142	
143	this.getServiceCharge = function(quantity, price) {
144		if (audit.showServiceCharge && quantity > 0) {
145			var ticketprice = parseFloat(price) / parseInt(quantity, 10);
146			if (typeof TicketGroup.list[ticketprice] !== 'undefined') {
147				var service_charge = TicketGroup.list[ticketprice]['sum_price'] - ticketprice;
148				if (service_charge > 0) {
149					return service_charge;
150				}
151			}
152		}
153		return 0;
154	};
155	
156	this.clearItems = function() {
157		this.removeItem(this.ticketCount, this.sumPrice);
158	};
159	
160	this.collect = function(sectorsDivId, seatsDivId) {
161		if (!this.collected) {
162			this.collected = true;
163			var self = this;
164			this.sumPrice = 0;
165			this.ticketCount = 0;
166			this.ticketPrices = 0;
167			this.serviceCharge = 0;
168			
169			$('#'+sectorsDivId).find('input.sector').each(function() {
170				var type = parseInt($(this).attr('data-type'));
171				var inbasket = parseInt($(this).attr('data-inbasket'));
172				if (inbasket > 0) {
173					if (type != 0) {
174						// capacity or arrival order - inbasket*price
175						var price = parseFloat($(this).attr('data-price'));
176						var ticketprice = parseFloat($(this).attr('data-ticketprice'));
177						self.addItem(inbasket, inbasket * price, inbasket * ticketprice);
178					} else {
179						// normal - inbasket, sumprice, sumticketprice
180						var sumprice = parseFloat($(this).attr('data-sumprice'));
181						var ticketprice = parseFloat($(this).attr('data-sumticketprice'));
182						if (inbasket > 0) {
183							self.addItem(inbasket, sumprice, ticketprice);
184						}
185					}
186				}
187			});
188			this.updateDisplay();
189		}
190	};
191	
192	this.updateDisplay = function() {
193		$('span.ticketCount').html(this.ticketCount);
194		$('span.ticketprice').html(it_price_format(this.ticketPrices));
195		$('span.servicecharge').html(it_price_format(this.serviceCharge));
196		$('span.total').html(it_price_format(this.sumPrice));
197		
198		if (this.ticketCount) {
199			$('button.toBasket').prop('disabled', false);
200		} else {
201			$('button.toBasket').prop('disabled', true);
202		}
203	};
204}
205
206function HTML5Audit() {
207	// variables
208	this.auditId;
209	this.eventId;
210	this.programId = 0;
211	this.noSectors;
212	this.loadingLabel = new DivLabel(Consts.domIds.loadingLabel);
213	this.sectorTooltip;
214	this.seatTooltip;
215	this.basketManager = new auditBasketManager();
216	this.updateInterval = false; // setInterval id for update
217	this.ajaxCallInProgress = 0;
218	this.width;
219	this.height;
220	this.isMobile = false;
221	this.capacityPopup = false;
222	this.auditLoaded = false;
223	this.ticketType = 'event';
224	this.initialized = false;
225	this.dynamic = false;
226	this.dynamicPrices = false;
227	this.refreshCounter = false;
228	this.basketProcessing = false;
229	this.showServiceCharge = false;
230	
231	this.collectingUpdate = false;
232	this.collectedSectors = [];
233	this.updating = false;
234	this.statusUpdateIgnore = [];
235	
236	//special sectors
237	this.bestTicketSectors = [];
238	this.availableBestTicketSectors = {};
239	this.hs = '';
240	
241	//audit seat status updates
242	this.updateElementList = [];
243	this.updateInProgress = false;
244	
245	//mlsz settings
246	this.mlszEnabled = false;
247	
248	//extends object exists
249	this.mlszAuditObjectExists = false;
250	this.bestTicketObjectExists = false;
251	
252	//special sectors: select all seats from sector
253	this.hasSpecialSectors = false;
254	this.specialSectors = [];
255	
256	this.obstructedIconSet = false;
257	this.obstructedIconShowed = false;
258	
259	//stage pictures
260	this.stagePictures = false;
261	
262	//distributed package mode
263	this.isDistributedPackage = false;
264	
265	//initialize object (once)
266	this.init = function() {
267		if (this.initialized) {
268			return false;
269		}
270		
271		//init ticket type (by default is: event)
272		if (typeof ticketType !== 'undefined') {
273			this.ticketType = ticketType;
274		}
275		
276		if (typeof dynamicsetup !== 'undefined') {
277			this.dynamicPrices = true;
278		}
279		
280		//add listeners
281		var self = this;
282		if (this.ticketType === 'event' || this.ticketType === 'seasonticket') {
283			document.addEventListener("loadSectorsData", function (e) {
284				if (!self.collectingUpdate) {
285					self.collectingUpdate = true;
286				}
287				if (self.statusUpdateIgnore.length === 0) {
288					self.statusUpdateIgnore = e.detail.ignore;
289				} else {
290					for(var i=0; i<e.detail.ignore.length; i++) {
291						self.statusUpdateIgnore.push(e.detail.ignore[i]);
292					}
293				}
294				self.statusUpdate(1, e.detail.update);
295			});
296			
297			if (this.ticketType === 'event') {
298				document.addEventListener("refreshSectorSeats", function (e) {
299					self.statusUpdate(0, [e.detail], 0, true, true);
300				});
301			}
302		}
303		
304		//init tooltips
305		this.setSeatTooltips();
306		this.setSectorTooltips();
307		
308		//setup dynamic mode
309		var auditObj = $('#' + Consts.domIds.audit);
310		if ($(auditObj).data('dynamic')) {
311			this.dynamic = true;
312			var setDynamicMode = new CustomEvent("setDynamicMode", { detail: true });
313			document.dispatchEvent(setDynamicMode);
314		}
315		
316		//init MLSZ mode
317		if ($(auditObj).data('mlsz')) {
318			this.mlszEnabled = true;
319		}
320		
321		//check venue soloseat
322		if (typeof venue_id !== 'undefined' && typeof Config.soloSeatProtectionVenue === 'object') {
323			venue_id = $.trim(venue_id);
324			if (Config.soloSeatProtectionVenue.hasOwnProperty(venue_id)) {
325				Config.soloSeatProtection = Config.soloSeatProtectionVenue[venue_id] ? true : false;
326			}
327		}
328		
329		//check object extends
330		if (this.mlszEnabled) {
331			this.mlszAuditObjectExists = typeof MLSZAudit !== 'undefined';
332		} else {
333			this.bestTicketObjectExists = typeof BestTicket !== 'undefined';
334		}
335		
336		//bestticket sector (not mlsz page)
337		if (this.dynamic && !this.mlszEnabled && this.bestTicketObjectExists) {
338			document.addEventListener("showBestTicketSelectorEvent", function (e) {
339				var sector_id = e.detail.sectorID;
340				var seat_group_id = e.detail.seatGroupID;
341				
342				//open popup
343				BestTicket.openSelector(sector_id, seat_group_id);
344			});
345		}
346		
347		//intialize protected sectors
348		ProtectedSectors.init();
349		
350		if (typeof dpstatus !== 'undefined') {
351			this.isDistributedPackage = true;
352			if (typeof DistributedPackage !== 'undefined') {
353				DistributedPackage.initStatus(dpstatus);
354			} else {
355				var initDPInt = setInterval(function() {
356					if (typeof DistributedPackage !== 'undefined') {
357						DistributedPackage.initStatus(dpstatus);
358					}
359					clearInterval(initDPInt);
360				}, 500);
361			}
362		}
363		
364		if (typeof hslist !== 'undefined' && hslist.length > 0) {
365			this.hs = hslist.join(',');
366		}
367		
368		//finalize init
369		this.initialized = true;
370		return true;
371	};
372	
373	//reset audit object
374	this.reinit = function() {
375		this.sectorTooltip = null;
376		this.seatTooltip = null;
377		this.capacityPopup = false;
378		this.auditLoaded = false;
379		this.hasSpecialSectors = false;
380		this.specialSectors = [];
381		var reinitAudit = new CustomEvent("reinitAudit");
382		document.dispatchEvent(reinitAudit);
383		this.setSeatTooltips();
384		this.setSectorTooltips();
385		this.stagePictures = false;
386		this.obstructedIconShowed = false;
387	};
388	
389	//calculate auditorium viewer size
390	this.calculateSize = function(auditObj) {
391		this.width = auditObj.width();
392		if (typeof this.width !== 'number') {
393			this.width = Config.auditBackgroundWidth;
394		}
395		
396		this.height = auditObj.height();
397		if (typeof this.height !== 'number') {
398			this.height = Config.auditBackgroundHeight;
399		}
400		if (this.height > screen.height) {
401			this.height = screen.height;
402		}
403		if (this.height > this.width) {
404			this.height = this.width;
405		}
406		auditObj.height(this.height);
407	};
408
409	//functions
410	this.loadAudit = function (auditId, eventId, noSectors, isMobile) {
411		this.init();
412		var auditObj = $('#' + Consts.domIds.audit);
413		if (!$('div#audit-viewer').size()) {
414			$(auditObj).prepend('<div id="audit-viewer" style="visibility:hidden;">');
415		}
416		
417		this.calculateSize(auditObj);
418		
419		if (!this.mlszEnabled) {
420			this.loadingLabel.setHeight(this.height);
421		}
422		this.auditId = auditId;
423		this.eventId = eventId;
424		this.noSectors = noSectors;
425		this.isMobile = isMobile;
426		
427		this.loadingLabel.showLabel(js_lang.audit_loading_audit);
428		auditObj.addClass('loading');
429		$('#'+Consts.domIds.sectorsLayer).css('visibility', 'hidden');
430		$('#'+Consts.domIds.seatsLayer).css('visibility', 'hidden');
431		
432		var data = {};
433		if (this.dynamic) {
434			this.noSectors = 0;
435			data.dynamic = 1;
436		}
437		if (this.hs !== '') {
438			data.hs = this.hs;
439		}
440		
441		var self = this;
442		self.ajaxCallInProgress++;
443		var url = '/auditview/audit/' + self.auditId;
444		$.ajax({
445			url: url,
446			type: 'get',
447			dataType: 'json',
448			data: data,
449			success: function(data, textStatus, jqXHR) {
450				self.ajaxCallInProgress--;
451				self.loadAuditSuccess(data);
452			},
453			error: function(jqXHR, textStatus, errorThrown) {
454				self.ajaxCallInProgress--;
455				ErrorManagement.sendError(new Error(0, js_lang.audit_error_loading_audit));
456			}
457		});
458	};
459	
460	this.loadAuditSuccess = function(data) {
461		data.mobileFastSelectionEnabled = Seat.mobileFastSelectionEnabled;
462		var loadAuditSuccessEvent = new CustomEvent("loadAuditSuccess", {
463			detail: data
464		});
465		document.dispatchEvent(loadAuditSuccessEvent);
466		
467		//save sector information
468		if (typeof data.snapshot !== 'undefined' && typeof data.snapshot.s !== 'undefined') {
469			for(i=0; i<data.snapshot.s.length; i++) {
470				var sector_id = data.snapshot.s[i].i;
471				var sector_name = data.snapshot.s[i].l.t;
472				var sector_key = 's' + sector_id;
473				sectorData[sector_key] = {
474					id: sector_id,
475					name: sector_name,
476					pricemin: 0,
477					pricemax: 0,
478					free: 0,
479					tooltip: '',
480					graphic: data.snapshot.s[i].t === 2 //sectortype: Fill
481						|| (data.snapshot.s[i].t === 1 && data.snapshot.s[i].n === 0)  //normal sector without seats
482				}
483			}
484		}
485		
486		//store special sectors
487		if (typeof data.specials !== 'undefined' && data.specials.length > 0) {
488			this.hasSpecialSectors = true;
489			this.specialSectors = data.specials;
490		}
491		
492		if (typeof data.obstructed_view_icon !== 'undefined' && data.obstructed_view_icon) {
493			this.obstructedIconSet = true;
494		}
495		
496		//stage pictures
497		if (typeof data.stage_pictures !== 'undefined' && data.stage_pictures) {
498			this.stagePictures = true;
499		}
500		
501		//disable animation if required
502		if ($('body').hasClass('acc-disable-animations')) {
503			this.setAnimation(false);
504		}
505	};
506	
507	this.loadAuditInitialStatus = function() {
508		var self = window.audit;
509		if (self.ajaxCallInProgress > 0) {
510			window.setTimeout(function() {
511				self.loadAuditInitialStatus();
512			}, 1000);
513			return false;
514		}
515		
516		var url = '';
517		var params = {};
518		
519		if (self.ticketType === 'event' || self.ticketType === 'reservation' || self.ticketType === 'seasonpackage') {
520			url = '/auditview/update';
521			params = {
522				audit_id: self.auditId,
523				event_id: self.eventId,
524				no_sectors: self.noSectors,
525				sector_ids: [],
526				initial_data: 1
527			};
528			if (self.programId > 0) {
529				params.program_id = self.programId;
530			}
531			if (self.ticketType === 'reservation') {
532				params.reservation = 1;
533			} else if (self.ticketType === 'seasonpackage') {
534				params.seasonpackage = 1;
535			}
536			if (self.dynamicPrices) {
537				params.dynprice = 1;
538			}
539		} else if (self.ticketType === 'seasonticket') {
540			url = '/seasonticket/audit_update';
541			params = {
542				venue_id: venue_id,
543				audit_id: self.auditId,
544				season_id: season_id,
545				seasonticket_id: self.eventId,
546				no_sectors: self.noSectors,
547				sector_id: 0,
548				initial_data: 1
549			};
550			if (audit.isDistributedPackage) {
551				params.distributedp_id = DistributedPackage.getId();
552			}
553		} else if (self.ticketType === 'freecombined') {
554			url = '/seasonticket/audit_update_freecombined';
555			params = {
556				venue_id: venue_id,
557				audit_id: self.auditId,
558				season_id: season_id,
559				seasonticket_id: seasonticket_id,
560				event_list: self.eventId,
561				no_sectors: self.noSectors,
562				sector_id: 0,
563				initial_data: 1,
564				seasontickettype: seasontickettype
565			};
566			FreeCombined.auditload();
567		}
568		
569		if (this.showServiceCharge) {
570			//force service charge
571			params.scen = 1;
572		}
573		if (this.hs !== '') {
574			params.hs = this.hs;
575		}
576		
577		if (url !== '') {
578			//valid ticket type
579			if (this.dynamic) {
580				params.dynamic = 1;
581				/*
582				if (!this.auditLoaded) {
583					//on dynamic mode first load must be in sector mode
584					params.sector = 1;
585				}
586				*/
587			}
588			
589			self.ajaxCallInProgress++;
590			$.ajax({
591				url: url,
592				type: 'get',
593				dataType: 'json',
594				data: params,
595				success: function(data, textStatus, jqXHR) {
596					self.ajaxCallInProgress--;
597					data.allSectorsLoaded = self.noSectors;
598					self.loadAuditInitialStatusSuccess(self, data);
599				},
600				error: function(jqXHR, textStatus, errorThrown) {
601					self.ajaxCallInProgress--;
602					ErrorManagement.sendError(new Error(0, js_lang.audit_error_loading_audit));
603				},
604				cache: false
605			});
606		} else {
607			//invalid ticket type
608			ErrorManagement.sendError(new Error(0, js_lang.audit_error_loading_audit));
609		}
610		
611		return true;
612	};
613	
614	this.loadAuditInitialStatusSuccess = function(self, data) {
615		if (!this.auditLoaded) {
616			if (self.mlszEnabled) {
617				//show delete icons for basket items
618				MLSZAudit.enableDeleteButtons();
619			}
620			
621			if (typeof bestTicketSectors !== 'undefined') {
622				this.bestTicketSectors = bestTicketSectors;
623			}
624			
625			//initialize bestTicketSectors
626			if (typeof data.sectorInitialInfos !== 'undefined' && this.bestTicketSectors.length > 0) {
627				this.availableBestTicketSectors = {};
628				for(var i in this.bestTicketSectors) {
629					var sectorID = this.bestTicketSectors[i];
630					if (typeof data.sectorInitialInfos[sectorID] !== 'undefined' && data.sectorInitialInfos[sectorID].isFree) {
631						//just available sectors are needed
632						this.availableBestTicketSectors[sectorID] = true;
633					}
634				}
635				
636				document.dispatchEvent(new CustomEvent("setBestTicketSectors", {
637					detail: this.availableBestTicketSectors
638				}));
639			}
640		}
641		
642		if (self.ticketType === 'freecombined') {
643			FreeCombined.auditloaded();
644		} else if (self.ticketType === 'seasonticket') {
645			if (typeof data.scen !== 'undefined' && data.scen) {
646				self.showServiceCharge = true;
647			}
648		}
649		
650		if (!this.auditLoaded) {
651			//check if audit contains capacity sectors, if required initialize the capacity popup object
652			this.auditLoaded = true;
653			var capacitySectorFound = false;
654			if (typeof data.sectorInitialInfos !== 'undefined') {
655				for(var i in data.sectorInitialInfos) {
656					if (typeof data.sectorInitialInfos[i].priceCatId !== 'undefined' && data.sectorInitialInfos[i].priceCatId) {
657						capacitySectorFound = true;
658						break;
659					}
660				}
661				
662				if (Testing.enabled) {
663					Testing.buildSectorList(data.sectorInitialInfos);
664				}
665			}
666			
667			if (capacitySectorFound) {
668				self.capacityPopup = new CapacityPopup();
669				$('#' + Consts.domIds.audit).on('mouseup touchend', function(e) {
670					self.capacityPopup.hide(e, false);
671				});
672			}
673		}
674
675		//init ticket prices
676		if (typeof data.reverse !== 'undefined' && data.reverse) {
677			TicketGroup.reverse = true;
678		}
679		TicketGroup.init(data.groups, data.sectors, data.groupspecial);
680		
681		data.domIds = {
682			sectorsElementId: Consts.domIds.sectorsLayer,
683			seatsElementId: Consts.domIds.seatsLayer
684		};
685		
686		//add sector group color (use owner smallest seat group)
687		var sectorGroups = {}, groupcol=0, sd={};
688		if (typeof data.sectors !== 'undefined') {
689			$.each(data.sectors, function(sector_id, sector_data) {
690				groupcol = typeof sector_data.groupcol !== 'undefined' ? sector_data.groupcol : 0;
691				sectorGroups[sector_id] = {
692					'groupcolor': groupcol
693				};
694			});
695		}
696		data.sectorGroups = sectorGroups;
697
698		var loadAuditInitialStatusSuccessEvent = new CustomEvent("loadAuditInitialStatusSuccess", {
699			detail: data
700		});
701		document.dispatchEvent(loadAuditInitialStatusSuccessEvent);
702
703		self.basketManager.collect(Consts.domIds.sectorsLayer, Consts.domIds.seatsLayer);
704
705		if (!this.basketProcessing) {
706			$('#'+Consts.domIds.audit).removeClass('loading');
707			self.loadingLabel.hide();
708		} else {
709			this.loadingLabel.showLabel(js_lang.label_your_basket_is_currently_in_payment + '<br />' + js_lang.label_goto_basket);
710		}
711
712		$('#audit').addClass('loaded');
713		$('div#audit-viewer').css({
714			'visibility': 'visible'
715		});
716		$('div#audit-viewer').hide();
717		$('div#audit-viewer').fadeIn();
718
719		if (self.ticketType === 'seasonticket' || self.ticketType === 'freecombined') {
720			self.startUpdate(); // Handle the frequently automatic update
721		}
722		
723		if (typeof data.sectors !== 'undefined') {
724			$.each(data.sectors, function(sector_id, sector_data) {
725				var sector_key = 's' + sector_id;
726				if (typeof sectorData[sector_key] !== 'undefined') {
727					sectorData[sector_key].pricemin = sector_data.min;
728					sectorData[sector_key].pricemax = sector_data.max;
729					if (typeof sector_data.free !== 'undefined') {
730						sectorData[sector_key].free = sector_data.free;
731					}
732					sectorData[sector_key].pricecat_ids = sector_data.pcid;
733					
734					if (!TicketGroup.countEnabled && sectorData[sector_key].free === 0) {
735						//when ticketcount is disabled and sector has free seats set count as 1
736						sectorData[sector_key].free = 1;
737					}
738				}
739			});
740		}
741		
742		TicketPriceCategory.updateSelectorPriceCategories(data);
743		if (typeof data.hasSecureSeat !== 'undefined' && data.hasSecureSeat) {
744			TicketGroup.showSecureLabel();
745		}
746		
747		//check obstructed view seat
748		if (this.obstructedIconSet && !this.obstructedIconShowed && typeof data.ticketInitialInfos !== 'undefined') {
749			for(var i in data.ticketInitialInfos) {
750				if (typeof data.ticketInitialInfos[i].is_ov !== 'undefined' && data.ticketInitialInfos[i].is_ov) {
751					$('#obv-seat-label').fadeIn();
752					this.obstructedIconShowed = true;
753					break;
754				}
755			}
756		}
757		
758		if (self.ticketType === 'event' && typeof Config !== 'undefined' && typeof Config.socket !== 'undefined' && Config.socket && parseInt(self.eventId, 10) > 0) {
759			var room_prefix = (typeof Config.socket.room !== 'undefined' && Config.socket.room !== '') ? Config.socket.room : '';
760			AuditSocket.init(Config.socket.host, Config.socket.port, room_prefix);
761		}
762	};
763	
764	this.setSectorTooltips = function() {
765		this.sectorTooltip = new Tooltip();
766		var self = this;
767		
768		if (!this.seatTooltip.waitingMode) {
769			//standard tooltip
770			document.addEventListener("highlightSector", function (e) {
771				self.sectorTooltip.showSectorsTooltip(e.detail);
772			});
773			
774			document.addEventListener("eliminateHighlight", function (e) {
775				self.sectorTooltip.mouseout();
776			});
777		} else {
778			//waiting tooltip
779			document.addEventListener("highlightSector", function (e) {
780				self.sectorTooltip.showSectorsTooltip(e.detail);
781			});
782			
783			document.addEventListener("eliminateHighlight", function (e) {
784				self.sectorTooltip.delayedHide();
785			});
786			
787			//common for sector and seat
788			var ttobj = $('#'+Consts.domIds.tooltip);
789			$(ttobj).on('mouseover', function() {
790				self.sectorTooltip.clearDelayedHide();
791			});
792			$(ttobj).on('mouseout', function() {
793				self.sectorTooltip.delayedHide();
794			});
795		}
796	};
797	
798	this.setSeatTooltips = function() {
799		this.seatTooltip = new Tooltip();
800		var self = this;
801		
802		if (!this.seatTooltip.waitingMode) {
803			//standard tooltip
804			document.addEventListener("highlightSeat", function (e) {
805				self.seatTooltip.showSeatsTooltip(e.detail);
806			});
807			
808			document.addEventListener("eliminateHighlight", function (e) {
809				self.seatTooltip.mouseout();
810			});
811		} else {
812			//waiting tooltip
813			document.addEventListener("highlightSeat", function (e) {
814				self.seatTooltip.showSeatsTooltip(e.detail);
815			});
816			
817			document.addEventListener("eliminateHighlight", function (e) {
818				self.seatTooltip.delayedHide();
819			});
820		}
821	};
822	
823	this.startUpdate = function() {
824		if (!this.isMobile && Config.updateFrequency > 0 && !this.updateInterval === false && !this.dynamic) {
825			this.updateInterval = window.setInterval(this.refreshAudit(), Config.updateFrequency);
826		}
827	};
828	
829	this.refreshAudit = function () {
830		this.refreshCounter = true;
831		var refreshAuditEvent = new CustomEvent("refreshAudit");
832		document.dispatchEvent(refreshAuditEvent);
833	};
834	
835	this.refreshAuditResult = function (elements) {
836		for (var i=0; i<elements.visibleSectors.length; i++) {
837			this.updateElementList.push({id: elements.visibleSectors[i], request: 'seat'});
838		}
839
840		this.updateElementList.push({id: elements.sectors, request: 'sector'});
841
842		for (var i=0; i<elements.dataLoadedSectors.length; i++) {
843			this.updateElementList.push({id: elements.dataLoadedSectors[i], request: 'seat'});
844		}
845
846		if (!this.updateInProgress) {
847			this.refreshFirstElement();
848		}
849	};
850	
851	this.refreshSector = function(sectorId, force, seat) {
852		var item = {
853			id: sectorId,
854			request: 'seat',
855			nosnapshot: true
856		};
857		if (typeof force !== 'undefined' && force === true) {
858			item.force = true;
859		}
860		if (typeof seat !== 'undefined') {
861			item.seat = seat.sector_id + ':' + seat.seat_id;
862		}
863		this.updateElementList.unshift(item);
864
865		if (!this.updateInProgress) {
866			this.refreshFirstElement();
867		}
868	};
869	
870	this.refreshSectorFast = function(sectorId) {
871		this.statusUpdate(0, [sectorId], 0, true, true, false, null, true);
872	};
873	
874	this.refreshFirstElement = function () {
875		if (this.updateElementList.length > 0) {
876			//update next sector
877			this.updateInProgress = true;
878			var element = this.updateElementList.shift();
879			var sectors = undefined;
880			var nosnapshot = false;
881			var force = false;
882			var seat = false;
883			if (element.request == 'seat') {
884				sectors = [element.id];
885				if (typeof element.nosnapshot !== 'undefined' && element.nosnapshot) {
886					nosnapshot = element.nosnapshot;
887				}
888				if (typeof element.force !== 'undefined') {
889					force = element.force;
890					if (force && typeof element.seat !== 'undefined') {
891						seat = element.seat;
892					}
893				}
894			}
895			this.statusUpdate(0, sectors, 0, nosnapshot, false, force, seat);
896		} else {
897			//no next item, updated process was finished
898			this.updateInProgress = false;
899			
900			//clear collecting mode
901			if (this.collectingUpdate) {
902				this.collectingUpdate = false;
903				//update all collected sectors together
904				if (this.collectedSectors.length > 0) {
905					var sectors = this.collectedSectors;
906					this.collectedSectors = [];
907					this.statusUpdate(1, sectors);
908				}
909			}
910			
911			if (this.refreshCounter) {
912				//update free tickets counter and mini basket
913				this.refreshCounter = false;
914				TicketGroup.countFreeTickets(true);
915			}
916		}
917	};
918	
919	this.statusUpdate = function(initData, sectors, retry, nosnapshot, loading, force, seat, fast) {
920		if (typeof initData === 'undefined') {
921			initData = 0;
922		}
923		if (typeof retry === 'undefined') {
924			retry = 0;
925		}
926		var self = window.audit;
927		if (typeof sectors !== 'undefined') {
928			var ns = 0;
929		} else {
930			sectors = [];
931			var ns = self.noSectors;
932		}
933		if (typeof loading === 'undefined') {
934			loading = false;
935		}
936		if (typeof force === 'undefined') {
937			force = false;
938		}
939		if (typeof fast === 'undefined') {
940			fast = false;
941		}
942		
943		var url = '';
944		var data = {};
945		
946		if (this.ticketType === 'event') {
947			url = '/auditview/update';
948			data = {
949				audit_id: self.auditId,
950				event_id: self.eventId,
951				no_sectors: ns,
952				sector_ids: sectors,
953				initial_data: initData
954			};
955			if (force) {
956				data.force = 1;
957			}
958			if (fast) {
959				data.fast = 1;
960			}
961		} else if (this.ticketType === 'seasonticket') {
962			url = '/seasonticket/audit_update';
963			data = {
964				venue_id: venue_id,
965				audit_id: self.auditId,
966				season_id: season_id,
967				seasonticket_id: self.eventId,
968				no_sectors: ns,
969				initial_data: initData,
970				sector_ids: sectors
971			};
972			if (this.dynamic) {
973				data.dynamic = 1;
974			}
975			if (force) {
976				data.force = 1;
977			}
978			if (fast) {
979				data.fast = 1;
980			}
981			if (self.isDistributedPackage) {
982				data.distributedp_id = DistributedPackage.getId();
983			}
984		}
985		if (url === '') {
986			return false;
987		}
988		
989		if (this.dynamic && (typeof nosnapshot === 'undefined' || !nosnapshot)) {
990			data.dynamic = 1;
991		}
992		if (self.ticketType === 'reservation') {
993			params.reservation = 1;
994		}
995		if (typeof data.force !== 'undefined' && data.force && typeof seat !== 'undefined') {
996			data.seat = seat;
997		}
998		
999		if (this.collectingUpdate && this.updating && sectors.length > 0) {
1000			for(var i=0; i<sectors.length; i++) {
1001				this.collectedSectors.push(sectors[i]);
1002			}
1003			return false;
1004		}
1005		
1006		this.updating = true;
1007		if (this.statusUpdateIgnore.length > 0) {
1008			data.ignore = this.statusUpdateIgnore;
1009			self.statusUpdateIgnore = [];
1010		}
1011		
1012		if (loading) {
1013			Seat.startLoading();
1014		}
1015		
1016		//check if the updatable sector is bestsector
1017		if (this.mlszEnabled && sectors.length === 1 && this.mlszAuditObjectExists) {
1018			//best - mlsz
1019			var sector_id = sectors[0];
1020			var sector_not_ignored = typeof data.ignore === 'undefined' || data.ignore.length === 0 || data.ignore.indexOf(sector_id) < 0;
1021			if (sector_not_ignored && MLSZAudit.isBestTicketSector(sector_id, true)) {
1022				//is a best ticket sector
1023				data.bestticket = 1;
1024			}
1025		} else if (this.bestTicketSectors.length > 0 && this.bestTicketObjectExists) {
1026			//best - normal
1027			var sector_id = sectors[0];
1028			var sector_not_ignored = typeof data.ignore === 'undefined' || data.ignore.length === 0 || data.ignore.indexOf(sector_id) < 0;
1029			if (sector_not_ignored && BestTicket.isBestTicketSector(sector_id, true)) {
1030				//is a best ticket sector
1031				data.bestticket = 1;
1032			}
1033		}
1034		
1035		$.ajax({
1036			url: url,
1037			type: 'get',
1038			dataType: 'json',
1039			data: data,
1040			success: function(data, textStatus, jqXHR) {
1041				self.updating = false;
1042				if (loading) {
1043					Seat.stopLoading();
1044				}
1045				if (self.dynamic && typeof data.snapshot !== 'undefined') {
1046					self.updateStructure(data.snapshot);
1047				}
1048				self.refreshFirstElement();
1049				self.statusUpdateSuccess(self, data, fast);
1050			},
1051			error: function() {
1052				self.updating = false;
1053				if (loading) {
1054					Seat.stopLoading();
1055				}
1056				if (retry < 3) {
1057					self.statusUpdate(initData, sectors, retry + 1, nosnapshot, loading, force, seat, fast);
1058				}
1059			},
1060			cache: false
1061		});
1062		
1063		return true;
1064	};
1065	this.statusUpdateSuccess = function(self, data, fast) {
1066		data.domIds = {
1067			sectorsElementId: Consts.domIds.sectorsLayer,
1068			seatsElementId: Consts.domIds.seatsLayer
1069		};
1070		
1071		var onlyupdate = typeof fast !== 'undefined' && fast;
1072		if (onlyupdate) {
1073			data.onlyupdate = true;
1074		}
1075		var statusUpdateSuccessEvent = new CustomEvent("statusUpdateSuccess", {
1076			detail: data
1077		});
1078		document.dispatchEvent(statusUpdateSuccessEvent);
1079		
1080		self.basketManager.collect(Consts.domIds.sectorsLayer, Consts.domIds.seatsLayer);
1081		
1082		if (Testing.enabled && Testing.sectorLoading > 0) {
1083			//collect available seats for testing
1084			var seats = [];
1085			if (typeof data.ticketInitialInfos !== 'undefined') {
1086				for(var seat_id in data.ticketInitialInfos) {
1087					//is an available seat in choosen sector
1088					if (data.ticketInitialInfos[seat_id].status === 0) {
1089						var seat = SeatingChart.getSeat(seat_id);
1090						if (seat && seat.sectorID === Testing.sectorLoading) {
1091							seats.push(parseInt(seat_id, 10));
1092						}
1093					}
1094				}
1095			}
1096			Testing.builidSeatList(seats);
1097		}
1098		
1099		if (typeof data.hasSecureSeat !== 'undefined' && data.hasSecureSeat) {
1100			TicketGroup.showSecureLabel();
1101		}
1102		
1103		if (onlyupdate && SeatingChart.counterFix.active) {
1104			SeatingChart.finishCountFix();
1105		}
1106		
1107		//check obstructed view seat
1108		if (typeof data.ticketInitialInfos !== 'undefined' && this.obstructedIconSet && !this.obstructedIconShowed) {
1109			for(var i in data.ticketInitialInfos) {
1110				if (typeof data.ticketInitialInfos[i].is_ov !== 'undefined' && data.ticketInitialInfos[i].is_ov) {
1111					$('#obv-seat-label').fadeIn();
1112					this.obstructedIconShowed = true;
1113					break;
1114				}
1115			}
1116		}
1117		
1118		//socket update - special sector
1119		if (onlyupdate && AuditSocket.started && typeof data.ticketInfos !== 'undefined' && typeof data.sectorInfos !== 'undefined'
1120		) {
1121			var sectorIDs =  Object.keys(data.sectorInfos);
1122			if (sectorIDs.length === 1) {
1123				AuditSocket.updateSector(parseInt(sectorIDs[0], 10));
1124			}
1125		}
1126	};
1127	
1128	//update audit structure (adding dynamically)
1129	this.updateStructure = function(snapshot) {
1130		var updateStructureEvent = new CustomEvent("updateStructure", {
1131			detail: snapshot
1132		});
1133		document.dispatchEvent(updateStructureEvent);
1134	};
1135	
1136	//open capacity selector popup
1137	this.openCapacityPopup = function(sector_id) {
1138		if (this.capacityPopup === false) {
1139			//no capacity popup initialized
1140			return false;
1141		}
1142		
1143		if (ProtectedSectors.enabled && ProtectedSectors.codeRequired(sector_id)) {
1144			ProtectedSectors.popup({
1145				"sector_id": sector_id,
1146				"seat_id": 0,
1147				"capacity": true
1148			});
1149			return false;
1150		}
1151		
1152		var j = $('div#Bsectors > input#sector_' + sector_id);
1153		
1154		if ($(j).size() !== 1) {
1155			//no sector data found
1156			return false;
1157		}
1158		
1159		var price = $(j).attr('data-price');
1160		if (typeof price === 'undefined') {
1161			//invalid sector info
1162			return false;
1163		}
1164		
1165		var pricecatid = $(j).attr('data-pricecatid');
1166		if (typeof pricecatid === 'undefined') {
1167			//invalid sector info
1168			return false;
1169		}
1170		
1171		var price = parseFloat($(j).attr('data-price'));
1172		var ticketprice = parseFloat($(j).attr('data-ticketprice'));
1173		var pricecatid = parseInt($(j).attr('data-pricecatid'), 10) ;
1174		var self = this;
1175		var e = null;
1176		
1177		//small waiting to load cursor position first
1178		startShowCapacity = true;
1179		setTimeout(function() {
1180			self.capacityPopup.showWithDatas(
1181				price, pricecatid,
1182				{
1183					"press_ok" : function(number) {
1184						//check ajax calls under process
1185						if (self.ajaxCallInProgress > 0) {
1186							window.setTimeout(function() {
1187								self.capacityPopup.button_ok.triggerHandler('click');
1188							}, 1000);
1189							return false;
1190						}
1191						
1192						//check quantity
1193						number = parseInt(number);
1194						if (isNaN(number) || number < 1 || number > 40) {
1195							revDialog({
1196								content: js_lang.audit_max_capacity_tickets,
1197								rightButton: {
1198									label: js_lang.interface_bmit,
1199									class: 'dark',
1200									close: true
1201								}
1202							});
1203							return false;
1204						}
1205						
1206						//preparte ticket selecting parameters
1207						var postData = {
1208							event_id: self.eventId,
1209							sector_id: sector_id,
1210							pricecat_id: pricecatid,
1211							price: ticketprice,
1212							quantity: number
1213						};
1214						if (self.showServiceCharge) {
1215							postData.service_charge = Math.max(0, price - ticketprice);
1216						}
1217						if (self.ticketType === 'seasonticket') {
1218							postData = {
1219								audit_id: self.auditId,
1220								event_id: self.eventId,
1221								type: 1,
1222								operation: 0,
1223								sector_id: sector_id,
1224								pricecat_id: pricecatid,
1225								price: price,
1226								quantity: number
1227							};
1228						} else if (self.ticketType === 'seasonpackage') {
1229							postData.audit_id = self.auditId;
1230						}
1231						
1232						//select tickets from a capacity sector
1233						Seat.selectCapacity(postData, j);
1234						
1235						//hide capacity selector popup
1236						self.capacityPopup.hide(null, true);
1237						return true;
1238					}
1239				}
1240			);
1241		}, 50);
1242		
1243		return true;
1244	};
1245	
1246	//update seat status on a best ticket sector
1247	this.updateBestTicketSector = function(sector_id, tickets_data) {
1248		document.dispatchEvent(new CustomEvent("updateSectorSeatStatus", { detail: {
1249			'sectorID': sector_id,
1250			'ticketsData': tickets_data
1251		}}));
1252	};
1253	
1254	//change animation status (like seat clicked and seat status changed)
1255	this.setAnimation = function(enabled) {
1256		document.dispatchEvent(new CustomEvent("sendActionToSeatingChart", { detail: {
1257			'action': 'setAnimation',
1258			'params': {
1259				'enabled': enabled
1260			}
1261		}}));
1262	};
1263}
1264
1265//ticket price groups
1266var TicketGroup = {
1267	countEnabled: true,
1268	categoryTooltipEnabled: true,
1269	
1270	//list of categories (price, color, count, index)
1271	list: {},
1272	listIndexed: {},
1273	firstLoad: true,
1274	reverse: false,
1275	
1276	//list of sectors for each price group
1277	sectors: {},
1278	
1279	//list of selected(highlighted) groups
1280	filtered: [],
1281	locked: [],
1282	filteredSectors: [],
1283	
1284	//category list container
1285	catListObj: false,
1286	
1287	//delayed unselect
1288	delayData: {
1289		unselectTime: 300, //0=disable
1290		timeOut: false, //setTimeout object
1291		groupId: 0 //group
1292	},
1293	
1294	//initialize ticket price groups and sectors
1295	init: function(groups, sectors, sepcialcolors) {
1296		if (typeof ticketcount_disable !== 'undefined' && ticketcount_disable === true) {
1297			this.countEnabled = false;
1298		}
1299		
1300		//init list
1301		if (typeof groups !== 'undefined') {
1302			this.list = groups;
1303		}
1304		
1305		//build group list
1306		this.catListObj = $('div.ticket-categories > ul');
1307		if (this.catListObj.length === 0) {
1308			//if group list object not found, create a hidden object
1309			var tempdiv = $('<div class="ticket-categories" style="display:none;">');
1310			var tempul = $('<ul>');
1311			$(tempdiv).append(tempul);
1312			$('body').append(tempdiv);
1313			this.catListObj = tempdiv;
1314		}
1315		var catlist_parent = $(this.catListObj).parent();
1316		
1317		//create price cateogory list
1318		$(catlist_parent).hide();
1319		
1320		if (!audit.showServiceCharge) {
1321			this.categoryTooltipEnabled = false;
1322			this.clearStatusBar();
1323		}
1324		
1325		var cssstyle = this.buildPriceList(false);
1326		$(catlist_parent).fadeIn();
1327		
1328		//add css styles for price category
1329		if (cssstyle !== '') {
1330			$(catlist_parent).append('<style>' + cssstyle + '</style>');
1331		}
1332		
1333		//init sector list for each price group
1334		if (typeof sectors !== 'undefined') {
1335			TicketGroup.sectors = {};
1336			$.each(sectors, function(sector_id, data) {
1337				if (typeof data.groups !== 'undefined') {
1338					for(var i=0; i<data.groups.length; i++) {
1339						var group_id = data.groups[i];
1340						if (typeof TicketGroup.sectors[group_id] === 'undefined') {
1341							TicketGroup.sectors[group_id] = [];
1342						}
1343						TicketGroup.sectors[group_id].push(sector_id);
1344					}
1345				}
1346			});
1347		}
1348		
1349		//add special colors
1350		if (typeof sepcialcolors !== 'undefined') {
1351			$.each(sepcialcolors, function(group, colors) {
1352				var listelement = $('<input id="special-' + group + '" type="hidden">');
1353				$(listelement).val(colors.color);
1354				$(listelement).attr('data-lowcolor', colors.lowcolor);
1355				$(catlist_parent).append(listelement);
1356			});
1357		}
1358		
1359		if (!this.countEnabled) {
1360			//remove counter html objects from price category list
1361			$('> li > span.ticketcount', this.catListObj).remove();
1362		}
1363		
1364		//count free tickets
1365		this.countFreeTickets();
1366	},
1367	
1368	//update groups (for dynamic prices)
1369	update: function(groups, sectors) {
1370		//clear filters
1371		if (this.filtered.length > 0) {
1372			this.filtered = [];
1373			this.locked = [];
1374			this.updateFilter();
1375		}
1376		
1377		//update price groups
1378		this.list = groups;
1379		
1380		//update sector groups
1381		this.sectors = {};
1382		$.each(sectors, function(sector_id, data) {
1383			if (typeof data.groups !== 'undefined') {
1384				for(var i=0; i<data.groups.length; i++) {
1385					var group_id = data.groups[i];
1386					if (typeof TicketGroup.sectors[group_id] === 'undefined') {
1387						TicketGroup.sectors[group_id] = [];
1388					}
1389					TicketGroup.sectors[group_id].push(sector_id);
1390				}
1391			}
1392		});
1393		
1394		if (this.catListObj.length === 1) {
1395			var catlist_parent = $(this.catListObj).parent();
1396			var cssstyle = this.buildPriceList(true);
1397			$('style', catlist_parent).html(cssstyle);
1398		}
1399	},
1400	
1401	//clreate price category list design
1402	buildPriceList: function(update) {
1403		var list_sample = $.trim($('div#ticket-category-sample').html());
1404		if (!list_sample) {
1405			//default price group item sample
1406			list_sample = '%price%<span style="display:none"> - <span class="count">%count%</span> ' + js_lang.label_pcs + '</span>';
1407		}
1408		
1409		var cssstyle = '';
1410		$(this.catListObj).empty();
1411		TicketGroup.listIndexed = {};
1412		
1413		var groupPosition = 0;
1414		$.each(this.list, function(price, group) {
1415			TicketGroup.listIndexed[group.index] = price;
1416			var classname = 'ticketpricecat' + group.index;
1417			var listelement, text;
1418			
1419			listelement = $('<li>').addClass('seat-available').addClass(classname).attr('id', 'grp' + group.index);
1420			$(listelement).data('price', group.price).data('type', 'seat');
1421			$(listelement).attr('data-color', group.color).attr('data-lowcolor', group.lowcolor).data('group', group.index);
1422			
1423			var price_str = /*audit.showServiceCharge ? group.sum_price_str :*/ group.price;
1424			text = list_sample.replace('%price%', price_str).replace('%count%', group.count);
1425			$(listelement).html(text);
1426			
1427			if (TicketGroup.categoryTooltipEnabled) {
1428				groupPosition++;
1429				var toolTipContent = TicketGroup.getTooltipContent(groupPosition, group);
1430				$(listelement).attr('title', toolTipContent);
1431			}
1432			
1433			if (!TicketGroup.reverse) {
1434				//prices asc. order
1435				$(TicketGroup.catListObj).append(listelement);
1436			} else {
1437				//prices desc. order
1438				$(TicketGroup.catListObj).prepend(listelement);
1439			}
1440			cssstyle += 'div.ticket-categories li.seat-available.' + classname + ':before { background-color: ' + group.color + '; } ';
1441		});
1442		
1443		if (update && this.countEnabled) {
1444			$('li > span', this.catListObj).show();
1445		}
1446		
1447		if (TicketGroup.categoryTooltipEnabled) {
1448			setTimeout(function() {
1449				$('li', TicketGroup.catListObj).qtip({
1450					style: {classes: 'dynClass'},
1451					content: {
1452						/*title: {text: js_lang.label_price_kategory},*/
1453						content: { text: $(this).attr('title') }
1454					},
1455					position: {
1456						at: "left center",
1457						my: "right center",
1458						viewport: $(window)
1459					},
1460					tip: true
1461				});
1462			}, 200);
1463		}
1464		
1465		return cssstyle;
1466	},
1467	
1468	//generate tooltip content for a price category
1469	getTooltipContent: function(position, group) {
1470		return '<div class="tip-title"> <span style="background-color:' + group.color + '"></span> ' + position + '. ' + js_lang.label_price_kategory.toLowerCase() + ' </div>'
1471			+ '<div class="tip-table">'
1472			+ '<div> <span class="tooltipTextLeft">
1472' + js_lang.audit_price + ':</span> <span class="tooltipTextRight">' + group.price + '</span> </div>'
1473			+ '<div> <span class="tooltipTextLeft">' + js_lang.basket_lang_basket_syscharge + ':</span> <span class="tooltipTextRight">' + group.service_charge_str + '</span> </div>'
1474			+ '<div> <span class="tooltipTextLeft">' + js_lang.audit_final_price + ':</span> <span class="tooltipTextRight">' + group.sum_price_str + '</span> </div>'
1475			+ '</div>';
1476	},
1477	
1478	//count free tickets for current event
1479	countFreeTickets: function(loadBasketData) {
1480		if (audit.ticketType === 'event' || audit.ticketType === 'reservation' || audit.ticketType === 'seasonpackage') {
1481			if (typeof loadBasketData === 'undefined') {
1482				loadBasketData = false;
1483			}
1484			var seasonpackage_mode = audit.ticketType === 'seasonpackage' ? 1 : 0;
1485			
1486			var event_id = audit.eventId;
1487			if (typeof event_id !== 'undefined' && event_id) {
1488				var params = {
1489					'event_id': event_id,
1490					'basket': loadBasketData ? 1 : 0
1491				};
1492				if (seasonpackage_mode) {
1493					params.sp = seasonpackage_mode;
1494				}
1495				if (audit.dynamicPrices) {
1496					params.dynprice = 1;
1497				}
1498				if (audit.hs !== '') {
1499					params.hs = audit.hs;
1500				}
1501				
1502				if (TicketGroup.countEnabled || loadBasketData || audit.dynamicPrices || seasonpackage_mode || Testing.enabled) {
1503					//update ticket count data via ajax
1504					$.ajax({
1505						url: '/auditview/ticketcount',
1506						type: 'post',
1507						dataType: 'json',
1508						data: params,
1509						cache: false,
1510						
1511						success: function(data, textStatus, jqXHR) {
1512							if (typeof data.success !== 'undefined' && data.success) {
1513								//update price categories
1514								if (typeof data.prices !== 'undefined') {
1515									$.each(data.prices, function(price, count) {
1516										if (typeof TicketGroup.list[price] !== 'undefined') {
1517											if (!TicketGroup.firstLoad) {
1518												//reset counter to zero on audit update
1519												TicketGroup.list[price]['count'] = 0;
1520											}
1521											
1522											if (TicketGroup.countEnabled) {
1523												TicketGroup.changeTicketCounter(price, count);
1524												$('li.ticketpricecat' + TicketGroup.list[price]['index'] + ' > span', TicketGroup.catListObj).fadeIn();
1525											}
1526										}
1527									});
1528								}
1529								
1530								//update mini basket
1531								if (loadBasketData && typeof data.basket !== 'undefined') {
1532									if (data.basket) {
1533										//basket has item(s)
1534										updateMiniBasket(data.basket.basketTime, data.basket.basketArray, data.basket.productCount, data.basket.priceTotalSum);
1535									} else {
1536										//basket is empty
1537										updateMiniBasket(0, {}, 0, 0);
1538									}
1539								}
1540								
1541								//update sectors free ticket count
1542								if (typeof data.sector !== 'undefined' && data.sector) {
1543									$.each(data.sector, function(sector_id, free_tickets) {
1544										var sector_key = 's' + sector_id;
1545										if (typeof sectorData[sector_key] !== 'undefined') {
1546											sectorData[sector_key].free = free_tickets;
1547										}
1548									});
1549									if (Testing.enabled) {
1550										Testing.setSectorFreeTickets(data.sector);
1551									}
1552								}
1553								
1554								//hide unavailable prices on seasonpackage
1555								if (seasonpackage_mode) {
1556									$('div.ticket-categories li.seat-available').each(function() {
1557										if (!$('span', $(this)).is(':visible')) {
1558											$(this).slideUp();
1559										}
1560									});
1561								}
1562							}
1563							
1564							if (TicketGroup.firstLoad) {
1565								TicketGroup.firstLoad = false;
1566							}
1567						}
1568					});
1569				} else {
1570					if (TicketGroup.firstLoad) {
1571						TicketGroup.firstLoad = false;
1572					}
1573				}
1574			}
1575		} else {
1576			$('li > span.ticketcount', this.catListObj).fadeIn();
1577		}
1578	},
1579	
1580	//increase counter for a group by price
1581	increaseTicket: function(price) {
1582		this.changeTicketCounter(price, 1);
1583	},
1584	
1585	//decrease counter for a group by price
1586	decreaseTicket: function(price) {
1587		this.changeTicketCounter(price, -1);
1588	},
1589	
1590	//change counter for a group by ticket price
1591	changeTicketCounter: function(price, diff) {
1592		if (this.countEnabled && typeof this.list[price] !== 'undefined') {
1593			var new_count = this.list[price]['count'] + diff;
1594			this.list[price]['count'] = new_count;
1595			$('li.ticketpricecat' + this.list[price]['index'] + ' span.count', this.catListObj).text(Math.max(0, new_count));
1596		}
1597	},
1598	
1599	//highlight a group
1600	select: function(group_id) {
1601		this.checkDelay();
1602		this.changeSelection(group_id, true, false);
1603	},
1604	
1605	//unhighlight a group
1606	unselect: function(group_id) {
1607		if (this.delayData.unselectTime > 0) {
1608			//delayed unselect
1609			TicketGroup.delayData.groupId = group_id;
1610			this.delayData.timeOut = setTimeout(function() {
1611				TicketGroup.delayedUnselect();
1612			}, TicketGroup.delayData.unselectTime);
1613		} else {
1614			//instant unselect
1615			this.changeSelection(group_id, false, false);
1616		}
1617	},
1618	
1619	//locked toggle selection
1620	toggleHoldSelection: function(group_id) {
1621		this.checkDelay();
1622		var add = this.locked.indexOf(group_id) < 0 ? true : false;
1623		this.changeSelection(group_id, add, true);
1624	},
1625	
1626	//delayed unselect: check if is in progress and stop it
1627	checkDelay: function() {
1628		if (this.delayData.unselectTime > 0 && this.delayData.timeOut !== false) {
1629			clearTimeout(this.delayData.timeOut);
1630			this.delayedUnselect();
1631		}
1632	},
1633	
1634	//delayed unselect: the process
1635	delayedUnselect: function() {
1636		this.delayData.timeOut = false;
1637		this.changeSelection(this.delayData.groupId, false, false);
1638	},
1639	
1640	//(un)highlight a group
1641	changeSelection: function(group_id, add, hold) {
1642		if (typeof this.listIndexed[group_id] !== 'undefined') {
1643			//mark hold property
1644			if (hold) {
1645				var categoryObj = $('li#grp' + group_id, this.catListObj);
1646				if (add) {
1647					this.locked.push(group_id);
1648					if (this.locked.length > 20) {
1649						this.clearLocked();
1650					}
1651					$(categoryObj).addClass('hold').addClass('highlighted');
1652				} else {
1653					var group_position = this.locked.indexOf(group_id);
1654					this.locked[group_position] = -1;
1655					$(categoryObj).removeClass('hold').removeClass('highlighted');
1656				}
1657			}
1658			
1659			//build next selection (filtered groups)
1660			var filter_changed = false;
1661			if (add) {
1662				if (this.filtered.indexOf(group_id) < 0) {
1663					//add group to filtered list if not exists
1664					this.filtered.push(group_id);
1665					filter_changed = true;
1666				}
1667			} else {
1668				if (this.locked.indexOf(group_id) < 0) {
1669					//remove group from filtered list
1670					var temp = [];
1671					for(var i=0; i<this.filtered.length; i++) {
1672						if (this.filtered[i] != group_id) {
1673							temp.push(this.filtered[i]);
1674						} else {
1675							filter_changed = true;
1676						}
1677					}
1678					this.filtered = temp;
1679				}
1680			}
1681			
1682			if (filter_changed) {
1683				this.updateFilter();
1684			}
1685		}
1686	},
1687	
1688	//clear locked list: remove "-1" elements
1689	clearLocked: function() {
1690		var temp = [];
1691		for(var i=0; i<this.locked.length; i++) {
1692			if (this.locked[i] >= 0) {
1693				temp.push(this.locked[i]);
1694			}
1695		}
1696		this.locked = temp;
1697	},
1698	
1699	//send new group filters to audit viewer
1700	updateFilter: function() {
1701		var params = {
1702			groups: TicketGroup.filtered,
1703			sectors: TicketGroup.getSectorsByGroups(TicketGroup.filtered),
1704			filteredSectors: TicketGroup.filteredSectors
1705		};
1706		var updateFilteredGroupsEvent = new CustomEvent("updateFilteredGroupsEvent", {
1707			detail: params
1708		});
1709		document.dispatchEvent(updateFilteredGroupsEvent);
1710	},
1711	
1712	//collect sectors contains some specified groups
1713	getSectorsByGroups: function(group_list) {
1714		var sectors = [];
1715		for(var i=0; i<group_list.length; i++) {
1716			if (typeof this.sectors[group_list[i]] !== 'undefined') {
1717				var sector_list = this.sectors[group_list[i]];
1718				for(var j=0; j<sector_list.length; j++) {
1719					var sector_id = parseInt(sector_list[j], 10);
1720					if (sectors.indexOf(sector_id) < 0) {
1721						sectors.push(sector_id);
1722					}
1723				}
1724			}
1725		}
1726		return sectors;
1727	},
1728	
1729	//get prices for a sector
1730	getSectorPrices: function(sector_id) {
1731		var list = [];
1732		sector_id = $.trim(sector_id);
1733		$.each(TicketGroup.sectors, function(group_index, sector_list) {
1734			if (sector_list.indexOf(sector_id) > -1) {
1735				if (typeof TicketGroup.listIndexed[group_index] !== 'undefined') {
1736					var group_id = TicketGroup.listIndexed[group_index];
1737					if (typeof TicketGroup.list[group_id] !== 'undefined') {
1738						var group = TicketGroup.list[group_id];
1739						group.pricenum = group_id;
1740						list.push(group);
1741					}
1742				}
1743			}
1744		});
1745		return list;
1746	},
1747	
1748	//return price group index by ticket price
1749	getGroupIndexByPrice: function(price) {
1750		return typeof this.list[price] !== 'undefined' ? this.list[price].index : false;
1751	},
1752	
1753	//display secure seat label when audit has secure seat
1754	showSecureLabel: function() {
1755		$('#secure-seat-label').fadeIn();
1756	},
1757	
1758	//clear selected seats status bar - show only ticket price (without service and total)
1759	clearStatusBar: function() {
1760		$('div.ticket-prices').each(function() {
1761			const $divs = $('>div', this);
1762			if ($divs.length === 3) {
1763				$divs.slice(1).hide();
1764			}
1765		});
1766	}
1767};
1768
1769function DivLabel(divId) {
1770	var loadingDiv = $('#'+divId);
1771	var loadingDivTxt = $('#'+divId+' > span');
1772	this.showLabel = function(label) {
1773		if (label) {
1774			loadingDivTxt.html(label);
1775		}
1776		loadingDiv.fadeIn(200);
1777	};
1778	this.hide = function() {
1779		loadingDiv.hide();
1780	};
1781	this.setHeight = function(h) {
1782		loadingDiv.height(h);
1783		loadingDivTxt.css('margin-top', ((h-100)/2)+'px');
1784	};
1785}
1786
1787function Error(errorCode,localizedMessage){
1788	this.errorCode = errorCode;
1789	this.localizedMessage = localizedMessage;
1790}
1791
1792var ErrorManagement = {
1793	customErrors: {
1794		e1: js_lang.label_recommended_tickets_no_delete_warning
1795	},
1796	
1797	sendErrorMessage: function(error_code) {
1798		if (typeof this.customErrors[error_code] !== 'undefined') {
1799			this.sendError(new Error(0, this.customErrors[error_code]));
1800		}
1801	},
1802	
1803	//open error message popup
1804	sendError: function(error) {
1805		switch (error.errorCode) {
1806			case 10003:
1807				revDialog({
1808					title: js_lang.audit_error_popup_title,
1809					content: error.localizedMessage,
1810					rightButton: {
1811						label: js_lang.admin_bmit,
1812						href: document.location.href+'?force_arrival=1',
1813						close: true,
1814						class: 'dark'
1815					}
1816				});
1817				break;
1818			
1819			case 99: //default with page reload
1820				var reload_url = document.location.href;
1821				var hash_pos = reload_url.indexOf('#');
1822				if (hash_pos > 0) {
1823					reload_url = reload_url.substring(0, hash_pos);
1824				}
1825				revDialog({
1826					title: js_lang.audit_error_popup_title,
1827					content: error.localizedMessage,
1828					rightButton: {
1829						label: js_lang.admin_bmit,
1830						href: reload_url,
1831						close: true,
1832						class: 'dark'
1833					}
1834				});
1835				break;
1836			
1837			case 1234: // just a warning
1838				revDialog({
1839					title: js_lang.label_attention,
1840					content: error.localizedMessage,
1841					rightButton: {
1842						label: js_lang.admin_bmit,
1843						close: true,
1844						class: 'dark'
1845					}
1846				});
1847				break;
1848			
1849			default:
1850				revDialog({
1851					title: js_lang.audit_error_popup_title,
1852					content: error.localizedMessage,
1853					rightButton: {
1854						label: js_lang.admin_bmit,
1855						close: true,
1856						class: 'dark'
1857					}
1858				});
1859				break;
1860		}
1861	}
1862};
1863
1864document.addEventListener("loadAuditError", function () {
1865	ErrorManagement.sendError(new Error(0, js_lang.audit_error_loading_audit));
1866});
1867
1868document.addEventListener("seatEvent", function (e) {
1869	Seat.click(e.detail.method, e.detail.seat);
1870});
1871
1872document.addEventListener("elementsResult", function (e) {
1873	audit.refreshAuditResult(e.detail);
1874});
1875
1876document.addEventListener("openCapacityEvent", function (e) {
1877	audit.openCapacityPopup(e.detail.sector_id);
1878});
1879
1880document.addEventListener("auditLoadedEvent", function (e) {
1881	if (!audit.noSectors) {
1882		audit.noSectors = e.detail.nosectors;
1883	}
1884	audit.loadAuditInitialStatus();
1885});
1886
1887document.addEventListener("syncSeatListEvent", function (e) {
1888	SeatingChart.seatList = e.detail.list;
1889	SeatingChart.seatOrderList = e.detail.order;
1890});
1891
1892document.addEventListener("seatingChartError", function (e) {
1893	var error_code = typeof e.detail !== 'undefined' ? e.detail : '';
1894	if (error_code !== '') {
1895		ErrorManagement.sendErrorMessage(error_code);
1896	}
1897});
1898
1899var Sector = {
1900	//return sector information
1901	get: function(sector_id) {
1902		var obj = $('div#Bsectors input#sector_' + sector_id);
1903		if ($(obj).size() !== 1) {
1904			return false;
1905		}
1906		
1907		return {
1908			id: sector_id,
1909			is_free: $(obj).attr('data-isfree') === '1',
1910			selected: $(obj).attr('data-inbasket') !== '0'
1911		};
1912	},
1913	
1914	//check if a sector has available tickets
1915	isAvailable: function(sector_id) {
1916		var item = this.get(sector_id);
1917		if (!item) {
1918			return false;
1919		}
1920		return item.is_free || item.selected;
1921	}
1922};
1923
1924var Seat = {
1925	seatKeys: false,
1926	seatRows: {},
1927	
1928	collectMode: false,
1929	collection: {},
1930	collectionObj: false,
1931	
1932	//mobile fast selection
1933	mobileFastSelectionEnabled: false,
1934	
1935	//enable mobile fast selection: on tap/click
1936	mobileFastSelection: function() {
1937		this.mobileFastSelectionEnabled = true;
1938	},
1939	
1940	//click event for seats
1941	click: function(method, seat) {
1942		if (method === 'add') {
1943			this.select(seat);
1944		} else if (method === 'remove') {
1945			this.unselect(seat);
1946		} else {
1947			this.fail();
1948		}
1949	},
1950	
1951	//select a seat
1952	select: function(seat) {
1953		if (this.collectMode) {
1954			//no really seat selection just collect them
1955			this.collect(true, seat);
1956			this.feedback(true);
1957			return true;
1958		}
1959		
1960		var inSpecialSector = audit.hasSpecialSectors
1961			&& audit.specialSectors.indexOf(seat.sector_id) >= 0
1962			&& audit.ticketType === 'event';
1963		
1964		//solo seat check (special sectors ignored)
1965		if (Config.soloSeatProtection && !inSpecialSector && !this.checkSelectSoloSeat(seat)) {
1966			audit.seatTooltip.hide();
1967			setTimeout(function() {
1968				ErrorManagement.sendError(new Error(0, js_lang.audit_error_no_solo_seat));
1969			}, 50);
1970			this.feedback(false);
1971			return false;
1972		}
1973		
1974		//linear seat check (special sectors ignored)
1975		if (Config.linearSeatProtection && !inSpecialSector && !this.checkSelectLinearSeat(seat)) {
1976			audit.seatTooltip.hide();
1977			setTimeout(function() {
1978				ErrorManagement.sendError(new Error(0, js_lang.audit_error_linear_seat));
1979			}, 50);
1980			this.feedback(false);
1981			return false;
1982		}
1983		
1984		//check seat status - must be free
1985		if (!this.seatStatus(seat.seat_id, Consts.statusIds.empty)) {
1986			this.feedback(false);
1987			return false;
1988		}
1989		
1990		//wc must be confirmed
1991		if (seat.iswc && !confirm(js_lang.audit_wheelchair_confirm)) {
1992			this.feedback(false);
1993			return false;
1994		}
1995		
1996		if (ProtectedSectors.enabled && ProtectedSectors.codeRequired(seat.sector_id)) {
1997			this.feedback(false);
1998			ProtectedSectors.popup(seat);
1999			return false;
2000		}
2001		
2002		//distributed package - select permission
2003		if (audit.isDistributedPackage && !DistributedPackage.canSelectSeat(true)) {
2004			return false;
2005		}
2006		
2007		//initalize request
2008		var url = '';
2009		var postData = {};
2010		
2011		if (audit.ticketType === 'event') {
2012			//standard event (default)
2013			var seatElement = SeatingChart.getSeat(seat.seat_id);
2014			url = '/auditview/select_seat';
2015			postData = {
2016				event_id: audit.eventId,
2017				seat_id: seat.seat_id,
2018				ticket_id: seat.ticket_id,
2019				sector_id: seat.sector_id
2020			};
2021			if (audit.showServiceCharge) {
2022				postData.price = seatElement.price;
2023				postData.service_charge = audit.basketManager.getServiceCharge(1, seatElement.price);
2024			}
2025		} else if (audit.ticketType === 'seasonticket') {
2026			//simple season ticket
2027			var seatElement = SeatingChart.getSeat(seat.seat_id);
2028			url = '/seasonticket/audit_select_seat';
2029			postData = {
2030				venue_id: venue_id,
2031				audit_id: audit.auditId,
2032				//event_id: self.eventId,
2033				seasonticket_id: audit.eventId,
2034				season_id: season_id,
2035				operation: 0, // 0 hozzaad 1 torol
2036				pricecat_id: seatElement.pricecatID,
2037				seat_id: seat.seat_id,
2038				sector_id: seat.sector_id,
2039				ticket_id: seat.ticket_id,
2040				type: 0 // 0 sima 1 kapacitasos
2041			};
2042			if (audit.showServiceCharge) {
2043				postData.price = seatElement.price;
2044				postData.service_charge = audit.basketManager.getServiceCharge(1, seatElement.price);
2045			}
2046			if (audit.isDistributedPackage) {
2047				if (!DistributedPackage.canSelectSeat(true)) {
2048					return false;
2049				}
2050				postData.distributedp_id = DistributedPackage.getId();
2051				if (typeof postData.price === 'undefined') {
2052					postData.price = seatElement.price;
2053				}
2054			}
2055		} else if (audit.ticketType === 'freecombined') {
2056			//free combined season ticket
2057			var seatElement = SeatingChart.getSeat(seat.seat_id);
2058			url = '/seasonticket/audit_select_freecombined_seat';
2059			postData = {
2060				venue_id: venue_id,
2061				audit_id: audit.auditId,
2062				event_id: audit.eventId,
2063				seasonticket_id: seasonticket_id,
2064				season_id: season_id,
2065				operation: 0, // 0 hozzaad 1 torol
2066				pricecat_id: seatElement.pricecatID,
2067				seat_id: seat.seat_id,
2068				sector_id: seat.sector_id,
2069				ticket_id: seat.ticket_id,
2070				type: 0, // 0 sima 1 kapacitasos
2071				seasontickettype: seasontickettype
2072			};
2073		} else if (audit.ticketType === 'reservation') {
2074			//reservation
2075			var seatElement = SeatingChart.getSeat(seat.seat_id);
2076			var TSeventID = typeof tsevent_id !== 'undefined' ? tsevent_id : 0;
2077			
2078			url = '/reservation/ticketoperation';
2079			postData = {
2080				audit_id: audit.auditId,
2081				event_id: audit.eventId,
2082				operation: 0, // 0 hozzaad 1 torol
2083				pricecat_id: seatElement.pricecatID,
2084				seat_id: seat.seat_id,
2085				sector_id: seat.sector_id,
2086				ticket_id: seat.ticket_id,
2087				type: 0, // 0 sima 1 kapacitasos
2088				tsevent_id: TSeventID
2089			};
2090		} else if (audit.ticketType === 'seasonpackage') {
2091			//season pachage
2092			url = '/seasonpackage/select_seat';
2093			postData = {
2094				event_id: audit.eventId,
2095				seat_id: seat.seat_id,
2096				ticket_id: seat.ticket_id,
2097				sector_id: seat.sector_id
2098			};
2099		}
2100		
2101		if (url === '') {
2102			//invalid tickettype
2103			ErrorManagement.sendError(new Error(0, js_lang.unsuccessful_reservation));
2104			this.feedback(false);
2105			return false;
2106		}
2107		
2108		//send request
2109		var self = this;
2110		this.startLoading();
2111		
2112		$.ajax({
2113			url: url,
2114			type: 'get',
2115			data: postData,
2116			dataType: 'json',
2117			cache: false,
2118			
2119			success: function(data) {
2120				self.stopLoading();
2121				if (data.result) {
2122					self.finalizeSeatSelection('add', seat.seat_id, data, inSpecialSector ? seat.sector_id : 0);
2123					if (audit.ticketType === 'seasonpackage') {
2124						SPBasketExpire.addItemsToSelections(1);
2125						if (typeof data.spbasket !== 'undefined') {
2126							SPBasketExpire.updateExpireTime(data.spbasket.time);
2127						}
2128					}
2129					if (typeof data.warning == 'string') {
2130						ErrorManagement.sendError(new Error(1234, data.warning));
2131					}
2132					if (audit.ticketType === 'seasonticket' && audit.isDistributedPackage) {
2133						DistributedPackage.increaseSelections(1);
2134						var thisprice = 0;
2135						if (typeof postData.price !== 'undefined') {
2136							thisprice += postData.price;
2137						}
2138						if (typeof postData.service_charge !== 'undefined') {
2139							thisprice += postData.service_charge;
2140						}
2141						DistributedPackage.updatePriceDisplay(DistributedPackage.status.current_price + thisprice);
2142					}
2143				} else {
2144					var errorcode = (typeof data.reload === 'undefined' || !data.reload) ? 0 : 99;
2145					ErrorManagement.sendError(new Error(errorcode, data.error.length ? data.error : js_lang.unsuccessful_reservation));
2146					var noRefresh = typeof (data.auditUpdate) !== 'undefined' && data.auditUpdate === false;
2147					if (audit.ticketType !== 'freecombined' && !noRefresh) {
2148						audit.refreshSector(seat.sector_id, true, seat);
2149					}
2150					
2151					//send feedback to audit viewer (fail)
2152					self.feedback(false);
2153				}
2154			},
2155			
2156			error: function(jqXHR, textStatus, errorThrown) {
2157				self.stopLoading();
2158				ErrorManagement.sendError(new Error(0, js_lang.unsuccessful_reservation));
2159				
2160				//send feedback to audit viewer (fail)
2161				self.feedback(false);
2162			}
2163		});
2164		
2165		return true;
2166	},
2167	
2168	//unselect the seat
2169	unselect: function(seat) {
2170		if (this.collectMode) {
2171			//no really seat unselection just collect selected seats
2172			this.collect(false, seat);
2173			this.feedback(true);
2174			return true;
2175		}
2176		
2177		var inSpecialSector = audit.hasSpecialSectors
2178			&& audit.specialSectors.indexOf(seat.sector_id) >= 0
2179			&& audit.ticketType === 'event';
2180		
2181		if (Config.soloSeatProtection && !inSpecialSector && !this.checkDeselectSoloSeat(seat)) {
2182			audit.seatTooltip.hide();
2183			ErrorManagement.sendError(new Error(0, js_lang.audit_error_no_solo_seat));
2184			this.feedback(false);
2185			return false;
2186		}
2187		
2188		if (Config.linearSeatProtection && !inSpecialSector && !this.checkDeselectLinearSeat(seat)) {
2189			audit.seatTooltip.hide();
2190			ErrorManagement.sendError(new Error(0, js_lang.audit_error_linear_seat));
2191			this.feedback(false);
2192			return false;
2193		}
2194		
2195		//check seat status - must be selected
2196		if (!this.seatStatus(seat.seat_id, Consts.statusIds.selected)) {
2197			this.feedback(false);
2198			return false;
2199		}
2200		
2201		//initalize request
2202		var url = '';
2203		var postData = {};
2204		
2205		if (audit.ticketType === 'event') {
2206			//standard event (default)
2207			url = '/auditview/deselect_seat';
2208			postData = {
2209				event_id: audit.eventId,
2210				seat_id: seat.seat_id,
2211				ticket_id: seat.ticket_id,
2212				sector_id: seat.sector_id
2213			};
2214		} else if (audit.ticketType === 'seasonticket') {
2215			//simple season ticket
2216			var seatElement = SeatingChart.getSeat(seat.seat_id);
2217			url = '/seasonticket/audit_deselect_seat';
2218			postData = {
2219				audit_id: audit.auditId,
2220				event_id: audit.eventId,
2221				operation: 1, // 0 hozzaad 1 torol
2222				pricecat_id: seatElement.pricecatID,
2223				seat_id: seat.seat_id,
2224				sector_id: seat.sector_id,
2225				ticket_id: seat.ticket_id,
2226				type: 0, // 0 sima 1 kapacitasos
2227				season_id: season_id,
2228				seasonticket_id: audit.eventId,
2229				venue_id: venue_id,
2230				price: seatElement.price
2231			};
2232			if (audit.isDistributedPackage) {
2233				postData.distributedp_id = DistributedPackage.getId();
2234			}
2235		} else if (audit.ticketType === 'freecombined') {
2236			//free combined season ticket
2237			var seatElement = SeatingChart.getSeat(seat.seat_id);
2238			url = '/seasonticket/audit_select_freecombined_seat';
2239			postData = {
2240				venue_id: venue_id,
2241				audit_id: audit.auditId,
2242				event_id: audit.eventId,
2243				seasonticket_id: seasonticket_id,
2244				season_id: season_id,
2245				operation: 1, // 0 hozzaad 1 torol
2246				pricecat_id: seatElement.pricecatID,
2247				seat_id: seat.seat_id,
2248				sector_id: seat.sector_id,
2249				ticket_id: seat.ticket_id,
2250				type: 0, // 0 sima 1 kapacitasos
2251				seasontickettype: seasontickettype
2252			};
2253		} else if (audit.ticketType === 'reservation') {
2254			//reservation
2255			var seatElement = SeatingChart.getSeat(seat.seat_id);
2256			var TSeventID = typeof tsevent_id !== 'undefined' ? tsevent_id : 0;
2257			
2258			url = '/reservation/ticketoperation';
2259			postData = {
2260				audit_id: audit.auditId,
2261				event_id: audit.eventId,
2262				operation: 1, // 0 hozzaad 1 torol
2263				pricecat_id: seatElement.pricecatID,
2264				seat_id: seat.seat_id,
2265				sector_id: seat.sector_id,
2266				ticket_id: seat.ticket_id,
2267				type: 0, // 0 sima 1 kapacitasos
2268				tsevent_id: TSeventID
2269			};
2270		} else if (audit.ticketType === 'seasonpackage') {
2271			//season pachage
2272			url = '/seasonpackage/deselect_seat';
2273			postData = {
2274				event_id: audit.eventId,
2275				seat_id: seat.seat_id,
2276				ticket_id: seat.ticket_id,
2277				sector_id: seat.sector_id
2278			};
2279		}
2280		
2281		if (url === '') {
2282			//invalid tickettype
2283			ErrorManagement.sendError(new Error(0, js_lang.label_unsuccess_deletion));
2284			return false;
2285		}
2286		
2287		//send request
2288		var self = this;
2289		this.startLoading();
2290		$.ajax({
2291			url: url,
2292			type: 'get',
2293			data: postData,
2294			dataType: 'json',
2295			cache: false,
2296			
2297			success: function(data) {
2298				self.stopLoading();
2299				if (data.result) {
2300					self.finalizeSeatSelection('remove', seat.seat_id, data, inSpecialSector ? seat.sector_id : 0);
2301					if (audit.ticketType === 'seasonpackage') {
2302						SPBasketExpire.addItemsToSelections(-1);
2303						if (typeof data.spbasket !== 'undefined') {
2304							SPBasketExpire.updateExpireTime(data.spbasket.time);
2305						}
2306					}
2307					
2308					if (audit.ticketType === 'seasonticket' && audit.isDistributedPackage) {
2309						DistributedPackage.increaseSelections(-1); //-1 = decrease
2310						var thisprice = 0;
2311						if (typeof postData.price !== 'undefined') {
2312							thisprice += postData.price;
2313						}
2314						if (audit.showServiceCharge) {
2315							thisprice += audit.basketManager.getServiceCharge(1, postData.price);
2316						}
2317						DistributedPackage.updatePriceDisplay(DistributedPackage.status.current_price - thisprice);
2318					}
2319				} else {
2320					ErrorManagement.sendError(new Error(0, data.error.length ? data.error : js_lang.label_unsuccess_deletion));
2321					audit.refreshSector(seat.sector_id);
2322					
2323					//send feedback to audit viewer (fail)
2324					self.feedback(false);
2325				}
2326			},
2327			
2328			error: function(jqXHR, textStatus, errorThrown) {
2329				self.stopLoading();
2330				ErrorManagement.sendError(new Error(0, js_lang.label_unsuccess_deletion));
2331				
2332				//send feedback to audit viewer (fail)
2333				self.feedback(false);
2334			}
2335		});
2336		
2337		return true;
2338	},
2339	
2340	//finalize seat selection on success
2341	finalizeSeatSelection: function(method, seat_id, data, special_sector_id) {
2342		var seat_list = [];
2343		if (typeof special_sector_id !== 'undefined' && special_sector_id > 0) {
2344			var seats = SeatingChart.getSectorSeats(special_sector_id);
2345			if (seats.length > 1) {
2346				for(var i=0; i<seats.length; i++) {
2347					seat_list.push(seats[i].id);
2348				}
2349			}
2350		}
2351		if (seat_list.length === 0) {
2352			seat_list.push(seat_id);
2353		}
2354		
2355		for(var i=0; i<seat_list.length; i++) {
2356			var list_seat_id = seat_list[i];
2357			var price = this.getTicketPrice(list_seat_id);
2358			var add2pb = false;
2359			
2360			if (method === 'add') {
2361				this.markSeatAsSelected(list_seat_id);
2362				TicketGroup.decreaseTicket(price);
2363				if (price !== -1) {
2364					audit.basketManager.addItem(1, price);
2365				}
2366				add2pb = true;
2367			} else if (method === 'remove') {
2368				this.markSeatAsUnselected(list_seat_id);
2369				TicketGroup.increaseTicket(price);
2370				if (price !== -1) {
2371					audit.basketManager.removeItem(1, price);
2372				}
2373			}
2374			
2375			if (add2pb && audit.ticketType !== 'seasonticket' && audit.ticketType !== 'seasonpackage') {
2376				//Prefixbox
2377				var productId = basic_url.split('-');
2378				var currency = $('[itemprop=priceCurrency]').attr('content');
2379				if (productId.length > 0) {
2380					productId = productId[productId.length - 1];
2381				}
2382				var title = $('div.programInfo h1').text();
2383				var params = {
2384					'price': (isNaN(price) || price === -1) ? 0 : price,
2385					'title': title,
2386					'productId': productId,
2387					'currency': currency,
2388					'send': false
2389				};
2390				$(document).trigger('IT:prefixboxAddToCart', params);
2391			}
2392		}
2393		
2394		//update mini basket
2395		if (audit.ticketType !== 'seasonpackage') {
2396			if (typeof(data.basketTime) != 'undefined') {
2397				updateMiniBasket(data.basketTime, data.basketArray, data.productCount, data.priceTotalSum);
2398			} else {
2399				updateMiniBasket(0, {}, 0, 0);
2400			}
2401		}
2402		
2403		if (audit.ticketType === 'freecombined') {
2404			var basket_data = typeof data.seat_session != 'undefined' ? data.seat_session : {};
2405			FreeCombined.updatecontinue(basket_data);
2406			FreeCombined.resetclock();
2407		}
2408		
2409		//update basket item list in MLSZ mode
2410		if (audit.mlszEnabled) {
2411			MLSZAudit.basketListUpdate(method, seat_id, data);
2412			if (audit.mlszAuditObjectExists) {
2413				var seat = SeatingChart.getSeat(seat_id);
2414				var sectorID = seat ? seat.sectorID : 0;
2415				if (MLSZAudit.isBestTicketSector(sectorID, true) && !SeatingChart.sectorHasSelection(sectorID)) {
2416					document.dispatchEvent(new CustomEvent("resetSectorData", { detail: {
2417						'sectorID': sectorID,
2418						'selected': false,
2419						'available': true
2420					}}));
2421				}
2422			}
2423		}
2424		
2425		//close bestticket sector if is empty after removing seat
2426		if (method === 'remove' && audit.bestTicketSectors.length > 0) {
2427			var seat = SeatingChart.getSeat(seat_id);
2428			var sectorID = seat ? seat.sectorID : 0;
2429			if (sectorID > 0 && BestTicket.isBestTicketSector(sectorID) && !SeatingChart.sectorHasSelection(sectorID)) {
2430				document.dispatchEvent(new CustomEvent("resetSectorData", { detail: {
2431					'sectorID': sectorID,
2432					'selected': false,
2433					'available': true
2434				}}));
2435			}
2436		}
2437		
2438		//send feedback to audit viewer (success)
2439		this.feedback(true);
2440		
2441		//sector reload status
2442		if (typeof data.sr !== 'undefined') {
2443			SeatingChart.initCountFix(seat_id);
2444			audit.refreshSectorFast(data.sr);
2445		}
2446		
2447		// Cross Selling
2448		if (method === 'remove' && typeof data.cs !== 'undefined') {
2449			$('[id^="crossselling_inbasket_"]').val('0');
2450			$('[id^="crossselling_inbasket_count_"] > span').html('0');
2451			$('[id^="crossselling_inbasket_count_"]').hide();
2452			for (var cs_id in data.cs) {
2453				for (var nete_id in data.cs[cs_id]) {
2454					$('#crossselling_inbasket_' + cs_id + '_' + nete_id).val(data.cs[cs_id][nete_id]);
2455					if (parseInt(data.cs[cs_id][nete_id]) > 0) {
2456						$('#crossselling_inbasket_count_' + cs_id + '_' + nete_id).show();
2457					} else {
2458						$('#crossselling_inbasket_count_' + cs_id + '_' + nete_id).hide();
2459					}
2460					$('#crossselling_inbasket_count_' + cs_id + '_' + nete_id + ' > span').html(data.cs[cs_id][nete_id]);
2461				}
2462			}
2463		}
2464		
2465		//socket sending
2466		if (AuditSocket.started) {
2467			if (method === 'add') {
2468				AuditSocket.selectSeat(seat_id)
2469			} else if (method === 'remove') {
2470				AuditSocket.unselectSeat(seat_id);
2471			}
2472		}
2473	},
2474	
2475	//select tickets from capacity sectors
2476	selectCapacity: function(postData, j) {
2477		var price = postData.price;
2478		var number = postData.quantity;
2479		
2480		var url = '/auditview/select_capacity';
2481		if (audit.ticketType === 'seasonticket') {
2482			url = '/seasonticket/audit_select_capacity';
2483		} else if (audit.ticketType === 'seasonpackage') {
2484			url = '/seasonpackage/select_capacity';
2485		} else if (audit.ticketType === 'reservation') {
2486			url = ''; //'/reservation/basket/put'; - no capacity sector in reservation
2487		}
2488		
2489		if (url !== '') {
2490			this.startLoading();
2491			audit.ajaxCallInProgress++;
2492			
2493			$.ajax({
2494				url: url,
2495				dataType: 'json',
2496				type: 'get',
2497				data: postData,
2498				cache: false,
2499				
2500				success: function(data) {
2501					if (audit.ticketType !== 'seasonticket' && audit.ticketType !== 'seasonpackage') {
2502						// Prefixbox
2503						var productId = basic_url.split('-');
2504						if (productId.length > 0) {
2505							productId = productId[productId.length - 1];
2506						}
2507						var title = $('div.programInfo h1').text();
2508						var params = {
2509							'price': isNaN(price) ? 0 : price * number,
2510							'productId': productId,
2511							'title': title,
2512							'count': number
2513						};
2514						$(document).trigger('IT:prefixboxAddToCart', params);
2515					}
2516					
2517					audit.ajaxCallInProgress--;
2518					Seat.stopLoading();
2519					
2520					if (data.result == true) {
2521						// sector inbasket
2522						var inbasket = parseInt(j.attr('inbasket'));
2523						inbasket += number;
2524						j.attr('inbasket', inbasket);
2525						
2526						// audit basket & mini basket update
2527						audit.basketManager.addItem(number, number * price);
2528						if (audit.ticketType !== 'seasonpackage') {
2529							updateMiniBasket(data.basketTime, data.basketArray, data.productC
2529ount, data.priceTotalSum);
2530						} else {
2531							//Season package updates
2532							SPBasketExpire.addItemsToSelections(number);
2533							if (typeof data.spbasket !== 'undefined') {
2534								SPBasketExpire.updateExpireTime(data.spbasket.time);
2535							}
2536						}
2537						
2538						//update sector status on audit viewer
2539						Seat.sectorSelect(postData.sector_id);
2540						TicketGroup.changeTicketCounter(price, -number);
2541						
2542						//update MLSZ ticket list
2543						if (audit.mlszEnabled && audit.mlszAuditObjectExists) {
2544							var ticketsData = typeof data.ticketsData !== 'undefined' ? data.ticketsData : [];
2545							if (ticketsData) {
2546								MLSZAudit.addAuditBasketItems(ticketsData);
2547							}
2548						}
2549						if (typeof data.warning == 'string') {
2550							ErrorManagement.sendError(new Error(1234, data.warning));
2551						}
2552					} else {
2553						ErrorManagement.sendError(new Error(0, data.error.length ? data.error : js_lang.unsuccessful_reservation));
2554					}
2555				},
2556				
2557				error: function(jqXHR, textStatus, errorThrown) {
2558					audit.ajaxCallInProgress--;
2559					Seat.stopLoading();
2560					ErrorManagement.sendError(new Error(0, js_lang.unsuccessful_reservation));
2561				}
2562			});
2563		} else {
2564			ErrorManagement.sendError(new Error(0, js_lang.unsuccessful_reservation));
2565		}
2566	},
2567	
2568	//best ticket selection
2569	bestTicketSelection: function(sector_id, price, quantity) {
2570		var sector_key = 's' + sector_id;
2571		price = parseInt(price, 10);
2572		if (typeof sectorData[sector_key] !== 'undefined' && typeof sectorData[sector_key].pricecat_ids[price] !== 'undefined') {
2573			var pricecat_id = sectorData[sector_key].pricecat_ids[price];
2574			var postData = {
2575				'event_id': audit.eventId,
2576				'sector_id': sector_id,
2577				'pricecat_id': pricecat_id,
2578				'price': price,
2579				'quantity': quantity
2580			};
2581			
2582			this.startLoading();
2583			$.ajax({
2584				url: '/auditview/select_bestticket',
2585				dataType: 'json',
2586				type: 'get',
2587				data: postData,
2588				cache: false,
2589				
2590				success: function(data) {
2591					if (audit.ticketType !== 'seasonticket') {
2592						// Prefixbox
2593						var productId = basic_url.split('-');
2594						if (productId.length > 0) {
2595							productId = productId[productId.length - 1];
2596						}
2597						var title = $('div.programInfo h1').text();
2598						var params = {
2599							'price': isNaN(price) ? 0 : price * quantity,
2600							'productId': productId,
2601							'title': title,
2602							'count': quantity
2603						};
2604						$(document).trigger('IT:prefixboxAddToCart', params);
2605					}
2606	
2607					audit.ajaxCallInProgress--;
2608					Seat.stopLoading();
2609					
2610					if (data.result == true) {
2611						// sector inbasket
2612						var j = $('div#Bsectors input#sector_' + sector_id);
2613						if ($(j).size() === 1) {
2614							var inbasket = parseInt(j.attr('inbasket'));
2615							inbasket += quantity;
2616							j.attr('inbasket', inbasket);
2617						}
2618						
2619						// audit basket & mini basket update
2620						audit.basketManager.addItem(quantity, quantity * price);
2621						if (audit.ticketType !== 'seasonpackage') {
2622							updateMiniBasket(data.basketTime, data.basketArray, data.productCount, data.priceTotalSum);
2623						}
2624						
2625						var ticketsData = typeof data.ticketsData !== 'undefined' ? data.ticketsData : [];
2626						if (ticketsData) {
2627							//update MLSZ ticket list
2628							if (audit.mlszEnabled && audit.mlszAuditObjectExists) {
2629								MLSZAudit.addAuditBasketItems(ticketsData);
2630							}
2631							
2632							//update selection groups
2633							var selection_group = [];
2634							for(var i=0; i<ticketsData.length; i++) {
2635								selection_group.push({
2636									'seatID': ticketsData[i].seat_id,
2637									'selectionGroup': ticketsData[i].group_guid
2638								});
2639								Seat.markSeatAsSelected(ticketsData[i].seat_id);
2640							}
2641							document.dispatchEvent(new CustomEvent("setSelectionGroup", { detail: selection_group }));
2642						}
2643						
2644						//update sector status on audit viewer
2645						Seat.sectorSelect(sector_id);
2646						
2647						audit.updateBestTicketSector(sector_id, ticketsData);
2648						TicketGroup.changeTicketCounter(price, -quantity);
2649					} else {
2650						var errorMessage = (typeof data.errorMessage !== 'undefined' && data.errorMessage !== '')
2651							? data.errorMessage
2652							: (data.error.length ? data.error : js_lang.unsuccessful_reservation);
2653						ErrorManagement.sendError(new Error(0, errorMessage));
2654					}
2655				},
2656				
2657				error: function(jqXHR, textStatus, errorThrown) {
2658					audit.ajaxCallInProgress--;
2659					Seat.stopLoading();
2660					ErrorManagement.sendError(new Error(0, js_lang.unsuccessful_reservation));
2661				}
2662			});
2663		}
2664	},
2665	
2666	//best ticket unselection by group
2667	bestTicketUnselection: function(sector_id, group, callback) {
2668		this.startLoading();
2669		audit.ajaxCallInProgress++;
2670		sector_id = parseInt(sector_id, 10);
2671		
2672		$.ajax({
2673			url: '/auditview/deselect_group',
2674			dataType: 'json',
2675			type: 'post',
2676			data: {
2677				'event_id': audit.eventId,
2678				'sector_id': sector_id,
2679				'group': group
2680			},
2681			cache: false,
2682			
2683			success: function(data) {
2684				audit.ajaxCallInProgress--;
2685				Seat.stopLoading();
2686				if (audit.mlszEnabled) {
2687					MLSZAudit.enableDeleteButtons();
2688				}
2689				
2690				if (data.result == true) {
2691					//BSector update (inbasket and sumprice data)
2692					var j = $('div#Bsectors input#sector_' + sector_id);
2693					if ($(j).size() === 1) {
2694						var inbasket = parseInt(j.attr('data-inbasket'), 10);
2695						inbasket -= data.removed.item_count;
2696						j.attr('data-inbasket', inbasket);
2697						
2698						var sumprice = parseFloat(j.attr('data-sumprice'));
2699						sumprice -= data.removed.item_sumprice;
2700						j.attr('data-sumprice', inbasket);
2701					}
2702					
2703					// audit basket & mini basket update
2704					audit.basketManager.removeItem(data.removed.item_count, data.removed.sum_price);
2705					if (audit.ticketType !== 'seasonpackage') {
2706						if (typeof data.basketArray !== 'undefined') {
2707							updateMiniBasket(data.basketTime, data.basketArray, data.productC
2707ount, data.priceTotalSum);
2708						} else {
2709							updateMiniBasket(0, {}, 0, 0);
2710						}
2711					}
2712					TicketGroup.changeTicketCounter(data.removed.item_price, data.removed.item_count);
2713					
2714					if (data.unselected_sector) {
2715						//all seats was unselected from sector
2716						//remove seats and reset the sector data + mark as available
2717						document.dispatchEvent(new CustomEvent("resetSectorData", { detail: {
2718							'sectorID': sector_id,
2719							'selected': false,
2720							'available': true
2721						}}));
2722					} else {
2723						//some selected seats remains in sector - update seat status list & sync seat status
2724						document.dispatchEvent(new CustomEvent("unselectMultipleSeats", { detail: {
2725							'sectorID': sector_id,
2726							'seats': data.removed.seats
2727						}}));
2728					}
2729					
2730					//call callback function to finalize the unselection (if is set)
2731					if (typeof callback === 'function') {
2732						callback(true);
2733					}
2734				} else {
2735					ErrorManagement.sendError(new Error(0, data.error.length ? data.error : js_lang.label_unsuccess_deletion));
2736					if (typeof callback === 'function') {
2737						callback(false);
2738					}
2739				}
2740			},
2741			
2742			error: function(jqXHR, textStatus, errorThrown) {
2743				audit.ajaxCallInProgress--;
2744				Seat.stopLoading();
2745				if (audit.mlszEnabled) {
2746					MLSZAudit.enableDeleteButtons();
2747				}
2748				ErrorManagement.sendError(new Error(0, js_lang.label_unsuccess_deletion));
2749			}
2750		});
2751	},
2752	
2753	//show loading message
2754	startLoading: function() {
2755		audit.seatTooltip.hide();
2756		audit.loadingLabel.showLabel(js_lang.audit_selecting_ticket);
2757		$('#' + Consts.domIds.audit).addClass('loading');
2758	},
2759	
2760	//hide loading message
2761	stopLoading: function() {
2762		audit.loadingLabel.hide();
2763		$('#' + Consts.domIds.audit).removeClass('loading');
2764	},
2765	
2766	//check solo seat on selection
2767	checkSelectSoloSeat: function(seat) {
2768		var neighbours = this.getNeighbours(seat, 2, 1);
2769		
2770		//cant select
2771		if ((neighbours.right1 === 0 && neighbours.right2 !== 0 && neighbours.endleft !== 2) || (neighbours.left1 === 0 && neighbours.left2 !== 0 && neighbours.endright !== 2)) {
2772			return false;
2773		} else if ((neighbours.right1 === 0 && neighbours.right2 === 1) || (neighbours.left1 === 0 && neighbours.left2 === 1)) {
2774			return false;
2775		}
2776		
2777		//selectable
2778		return true;
2779	},
2780	
2781	//check solo seat on unselect
2782	checkDeselectSoloSeat: function(seat) {
2783		var neighbours = this.getNeighbours(seat, -1, 1);
2784		
2785		//unselectable
2786		if ((neighbours.endright !== 0 && neighbours.endleft !== 0) && (neighbours.left1 !== 1 || neighbours.right1 !== 1)) {
2787			return true;
2788		} else if (neighbours.left1 === 0 || neighbours.right1 === 0) {
2789			return true;
2790		}
2791		
2792		//cant unselect
2793		return false;
2794	},
2795	
2796	//check linear seat on selection (ids in the row must be picked in increasing order)
2797	checkSelectLinearSeat: function(seat) {
2798		var row = this.getSeatRow(seat).slice().sort(function(a, b) {
2799			return a.id - b.id;
2800		});
2801		
2802		//the seat being selected must be the first empty seat in the row
2803		for (var i = 0; i < row.length; i++) {
2804			if (row[i].status === Consts.statusIds.empty) {
2805				return row[i].id === seat.seat_id;
2806			}
2807		}
2808		
2809		return false;
2810	},
2811	
2812	//check linear seat on unselect (only the last selected seat in the row may be released)
2813	checkDeselectLinearSeat: function(seat) {
2814		var row = this.getSeatRow(seat).slice().sort(function(a, b) {
2815			return a.id - b.id;
2816		});
2817		
2818		//the seat being deselected must be the last selected seat in the row
2819		for (var i = row.length - 1; i >= 0; i--) {
2820			if (row[i].status === Consts.statusIds.selected) {
2821				return row[i].id === seat.seat_id;
2822			}
2823		}
2824		
2825		return false;
2826	},
2827	
2828	//collect seat's neighbours
2829	getNeighbours: function(seat, default_status, end_status) {
2830		var row = this.getSeatRow(seat);
2831		var list = {
2832			'left1': default_status,
2833			'left2': default_status,
2834			'right1': default_status,
2835			'right2': default_status,
2836			'endleft': default_status,
2837			'endright': default_status
2838		};
2839		
2840		var index = -1;
2841		$.each(row, function(seat_index, seat_data) {
2842			if (seat_data.id === seat.seat_id) {
2843				index = seat_index;
2844			}
2845		});
2846		
2847		if (index >= 0) {
2848			//left seats
2849			if (index > 0) {
2850				list.left1 = row[index - 1].status;
2851				if (index > 1) {
2852					list.left2 = row[index - 2].status;
2853				}
2854			}
2855			
2856			//right seats
2857			if (index < (row.length - 1)) {
2858				list.right1 = row[index + 1].status;
2859				if (index < (row.length - 2)) {
2860					list.right2 = row[index + 2].status;
2861				}
2862			}
2863			
2864			//left-end
2865			var i;
2866			for(i=index-1; i>=0; i--) {
2867				if (row[i].status !== end_status) {
2868					break;
2869				}
2870			}
2871			list.endleft = i >= 0 ? row[i].status : default_status;
2872			
2873			//right-end
2874			for(i=index+1; i<row.length; i++) {
2875				if (row[i].status !== end_status) {
2876					break;
2877				}
2878			}
2879			list.endright = i < row.length ? row[i].status : default_status;
2880		}
2881		
2882		return list;
2883	},
2884	
2885	//return entire row for a seat
2886	getSeatRow: function(seat) {
2887		if (!this.seatKeys) {
2888			this.seatKeys = Object.keys(SeatingChart.seatList);
2889		}
2890		
2891		//seat properties
2892		var sector_id = seat.sector_id;
2893		var row_id = seat.row_id;
2894		
2895		//collect seats from row
2896		var cache_key = sector_id + '-' + row_id;
2897		var seats = typeof this.seatRows[cache_key] !== 'undefined' ? this.seatRows[cache_key] : [];
2898		if (seats.length === 0) {
2899			for(var i=0; i<SeatingChart.seatOrderList.length; i++) {
2900				var seat_id = SeatingChart.seatOrderList[i];
2901				var seat_data = SeatingChart.seatList[seat_id];
2902				if (seat_data.sectorID === sector_id && seat_data.rowID === row_id) {
2903					seats.push(parseInt(seat_id, 10));
2904				}
2905			}
2906			/*
2907			$.each(SeatingChart.seatList, function(seat_id, seat_data) {
2908				if (seat_data.sectorID === sector_id && seat_data.rowID === row_id) {
2909					seats.push(parseInt(seat_id, 10));
2910				}
2911			});
2912			*/
2913			this.seatRows[cache_key] = seats;
2914		}
2915		
2916		var list = [];
2917		var seatElement;
2918		for(var i=0; i<seats.length; i++) {
2919			seatElement = SeatingChart.getSeat(seats[i]);
2920			if (seatElement) {
2921				list.push({
2922					id: seats[i],
2923					status: seatElement.status
2924				});
2925			}
2926		}
2927		
2928		return list;
2929	},
2930	
2931	//check if a seat has a required status
2932	seatStatus: function(seat_id, required_status) {
2933		var seatElement = SeatingChart.getSeat(seat_id);
2934		if (seatElement) {
2935			var status = seatElement.status;
2936			if (typeof status !== 'undefined' && parseInt(status, 10) === required_status) {
2937				return true;
2938			}
2939		}
2940		
2941		return false;
2942	},
2943	
2944	//return ticket price by seat
2945	getTicketPrice: function(seat_id) {
2946		var price = -1;
2947		var seatElement = SeatingChart.getSeat(seat_id);
2948		
2949		if (seatElement) {
2950			var value = seatElement.price;
2951			if (typeof value !== 'undefined') {
2952				price = value;
2953			}
2954		}
2955		
2956		return price;
2957	},
2958	
2959	//change seat status to selected
2960	markSeatAsSelected: function(seat_id) {
2961		this.changeStatus(seat_id, 1);
2962	},
2963	
2964	//change seat status to free
2965	markSeatAsUnselected: function(seat_id) {
2966		this.changeStatus(seat_id, 0);
2967	},
2968	
2969	//change status on html seat list
2970	changeStatus: function(seat_id, status) {
2971		SeatingChart.updateSeatStatus(seat_id, status);
2972	},
2973	
2974	//change status on html seat list
2975	changeStatusWithRefresh: function(seat_id, status, group_id) {
2976		SeatingChart.updateSeatStatus(seat_id, status, true, group_id);
2977	},
2978	
2979	//click event failed
2980	fail: function() {
2981		this.feedback(false);
2982	},
2983	
2984	//send seat event results to viewer
2985	feedback: function(success) {
2986		var seatEventResult = new CustomEvent("seatEventResult", { detail: {
2987			'success': success
2988		}});
2989		document.dispatchEvent(seatEventResult);
2990	},
2991	
2992	//remove selections from audit viewer
2993	clearSelection: function() {
2994		var clearSelectionEvent = new CustomEvent("clearSelectionEvent");
2995		audit.basketManager.clearItems();
2996		document.dispatchEvent(clearSelectionEvent);
2997		
2998		//audit.refreshAudit();
2999		TicketGroup.countFreeTickets(false);
3000		
3001		//$('div#Bseats > input[data-status="1"]').attr('data-status', 0);
3002		$('div#Bsectors > input:not([data-inbasket="0"])').attr('data-inbasket', 0).attr('data-sumprice', 0);
3003	},
3004	
3005	//send sector selection evento to audit viewer
3006	sectorSelect: function(sector_id) {
3007		var sectorSelectionResult = new CustomEvent("sectorSelectionResult", { detail: {
3008			'sectorID': sector_id
3009		}});
3010		document.dispatchEvent(sectorSelectionResult);
3011	},
3012	
3013	//collect selected seats
3014	collect: function(add, seat) {
3015		var sector_id = typeof seat.sector_id !== 'undefined' ? seat.sector_id : 0;
3016		var seat_id = typeof seat.seat_id !== 'undefined' ? seat.seat_id : 0;
3017		
3018		if (sector_id > 0 && seat_id > 0) {
3019			if (add) {
3020				//select a new seat
3021				if (typeof this.collection[sector_id] === 'undefined') {
3022					this.collection[sector_id] = [];
3023				}
3024				this.collection[sector_id].push(seat_id);
3025			} else {
3026				//seat unselection
3027				if (typeof this.collection[sector_id] !== 'undefined') {
3028					var pos = this.collection[sector_id].indexOf(seat_id);
3029					if (pos >= 0) {
3030						this.collection[sector_id].splice(pos, 1);
3031					}
3032					if (!this.collection[sector_id].length) {
3033						delete this.collection[sector_id];
3034					}
3035				}
3036			}
3037			
3038			if (this.collectionObj === false) {
3039				var box = $('<div id="seat_collection" style="position:fixed; top: 10px; left: 10px; width: 200px; height: 20px; overflow:hidden; z-index:15000;">').html('');
3040				$("body").append(box);
3041				this.collectionObj = $('<input type="text" id="collection_input" style="width: 200xp; height: 18px; border: solid 1 px #000" onclick="this.select()">');
3042				$(box).append(this.collectionObj);
3043			}
3044			
3045			var collect_json = JSON.stringify(this.collection);
3046			$(this.collectionObj).val(collect_json);
3047		}
3048	}
3049};
3050
3051
3052var startShowCapacity = false;
3053function CapacityPopup() {
3054	var capacityIsShowed = false;
3055	var capacityPopupDiv = $('#'+Consts.domIds.capacityPopup);
3056	var button_ok;
3057	var button_minus;
3058	var button_plus;
3059	var inp_number;
3060	var price;
3061	var service_charge;
3062	var total;
3063	var mouseX=0, mouseY=0;
3064	var priceId = 0;
3065	
3066	var init = function() {
3067		inp_number  = $('<input type="text" onclick="" id="inp_numberCapacityPopup" style="margin:0 6px;width:50px;display:inline-block;text-align:center;vertical-align:middle" />');
3068		button_ok = $('<input type="button" class="button right dark" value="ok" id="button_OKcapacityPopup" />');
3069		button_minus = $('<input type="button" class="button" value="-" id="button_minuscapacityPopup" style="width:33px;height:33px;min-width:unset" />');
3070		button_plus = $('<input type="button" class="button" value="+" id="button_pluscapacityPopup" style="width:33px;height:33px;min-width:unset" />');
3071		price = $('<p style="display:inline;font-weight:bold;"></p>');
3072		service_charge = $('<p style="display:inline;font-weight:bold;"></p>');
3073		total = $('<p style="display:inline;font-weight:bold;"></p>');
3074		var br = '<br />';
3075		capacityPopupDiv.html('');
3076		capacityPopupDiv.append(js_lang.audit_price + ': ');
3077		capacityPopupDiv.append(price);
3078		if (audit.showServiceCharge) {
3079			capacityPopupDiv.append(br);
3080			capacityPopupDiv.append(js_lang.basket_lang_basket_syscharge + ': ');
3081			capacityPopupDiv.append(service_charge);
3082			capacityPopupDiv.append(br);
3083			capacityPopupDiv.append(js_lang.audit_final_price + ': ');
3084			capacityPopupDiv.append(total);
3085		}
3086		capacityPopupDiv.append(br);
3087		capacityPopupDiv.append(js_lang.audit_quantity + ': ');
3088		capacityPopupDiv.append(br);
3089		capacityPopupDiv.append($('<div>').append(button_minus).append(inp_number).append(button_plus).addClass('fullMarginBottom smallMarginTop'));
3090		capacityPopupDiv.append(button_ok);
3091		capacityPopupDiv.append('<style>#inp_numberCapacityPopup { text-indent:0;transition:text-indent 30ms linear; } .capacity_movetoleft { text-indent: -35px!important; } .capacity_movetoright { text-indent: 16px!important; } .capacity_movestop { transition:text-indent 0ms!important; }</style>');
3092		
3093		//store mouse position on mouseup
3094		$(document).on("mouseup touchend", function(event) {
3095			mouseX = event.pageX;
3096			mouseY = event.pageY;
3097		});
3098	};
3099	
3100	init();
3101	
3102	return {
3103		"showWithDatas": function(priceVal, priceCatId, delegates) {
3104			price.html(it_price_format(priceVal));
3105			var serviceChargeVal = audit.basketManager.getServiceCharge(1, priceVal);
3106			service_charge.html(it_price_format(serviceChargeVal));
3107			total.html(it_price_format(priceVal + serviceChargeVal));
3108			var okDelegate = delegates.press_ok;
3109			
3110			if (!capacityIsShowed || priceId != priceCatId) {
3111				inp_number.val('1');
3112			}
3113			priceId = priceCatId;
3114			
3115			button_ok.click(function() {
3116				if ($.isNumeric(inp_number.val())) {
3117					okDelegate(inp_number.val());
3118				} else {
3119					okDelegate(undefined);
3120				}
3121			});
3122			
3123			var qchanging = false;
3124			button_minus.unbind('click').bind('click', function() {
3125				if (!qchanging) {
3126					var val = inp_number.val(),
3127						newVal = ($.isNumeric(val) && parseInt(val) > 1) ? parseInt(val) - 1 : 1;
3128					
3129					if (parseInt(val, 10) !== parseInt(newVal, 10)) {
3130						inp_number.addClass('capacity_movestop').addClass('capacity_movetoright');
3131						qchanging = true;
3132						setTimeout(function() {
3133							inp_number.val(newVal);
3134							inp_number.removeClass('capacity_movestop').removeClass('capacity_movetoright');
3135							qchanging = false;
3136						}, 10);
3137					}
3138				}
3139			});
3140			button_plus.unbind('click').bind('click', function() {
3141				if (!qchanging) {
3142					var val = inp_number.val(),
3143						newVal = ($.isNumeric(val) && parseInt(val) < 40) ? parseInt(val) + 1 : 40;
3144					
3145					if (parseInt(val, 10) !== parseInt(newVal, 10)) {
3146						inp_number.addClass('capacity_movestop').addClass('capacity_movetoleft');
3147						qchanging = true;
3148						setTimeout(function() {
3149							inp_number.val(newVal);
3150							inp_number.removeClass('capacity_movestop').removeClass('capacity_movetoleft');
3151							qchanging = false;
3152						}, 10);
3153					}
3154				}
3155			});
3156			
3157			var auditpos = $('#'+Consts.domIds.audit).offset();
3158			var x = mouseX-auditpos.left-10;
3159			var y = mouseY-auditpos.top-10;
3160			
3161			capacityPopupDiv.css('top', y+'px');
3162			capacityPopupDiv.css('left', x+'px');
3163			capacityPopupDiv.css('z-index', 1);
3164			capacityPopupDiv.show();
3165			capacityIsShowed = true;
3166			setTimeout(function() {
3167				startShowCapacity = false;
3168			}, 100);
3169		},
3170		
3171		"hide": function(e, isForce) {
3172			if (isForce || (!startShowCapacity && capacityIsShowed && !$.contains(capacityPopupDiv[0], e.target))) {
3173				button_ok.unbind('click');
3174				capacityIsShowed = false;
3175				capacityPopupDiv.hide();
3176				return true;
3177			}
3178			return false;
3179		},
3180		
3181		"button_ok": button_ok
3182	};
3183}
3184
3185var mousePos = {};
3186var sectorData = {};
3187var delayTimer = false;
3188
3189function Tooltip() {
3190	var popupdiv = $('#'+Consts.domIds.tooltip),
3191		showTimer = false,
3192		auditId = window.netaudit_id,
3193		currentSeat = false,
3194
3195		tooltipObj = {
3196			waitingMode: typeof waiting_tooltip !== 'undefined' && waiting_tooltip,
3197			stageImage360: typeof auditview_stageimg360 !== 'undefined' && auditview_stageimg360,
3198			sitelang: typeof current_language !== 'undefined' ? current_language : 'hu',
3199			
3200			'over': false,
3201			'arrowOrientation': {
3202				'X': 'arrowleft',
3203				'Y': 'arrowmiddle'
3204			},
3205			'showSectorsTooltip': function(j) {
3206				var sector_id = j.id.sectorID;
3207				clearTimeout(showTimer);
3208				
3209				showTimer = setTimeout(function() {
3210					if (tooltipObj.waitingMode) {
3211						tooltipObj.clearDelayedHide();
3212					}
3213					
3214					var name = '';
3215					var sector_key = 's' + sector_id;
3216					var more = false;
3217					
3218					if (typeof sectorData[sector_key] !== 'undefined') {
3219						if (sectorData[sector_key].tooltip === '') {
3220							//add sector name
3221							if (!sectorData[sector_key].graphic || sectorData[sector_key].free > 0) {
3222								name = '<div><span class="tooltipTextLeft">' + js_lang.audit_sector + ':</span><span class="tooltipTextRight">' + sectorData[sector_key].name + '</span></div>';
3223								//add more data only when sector has available tickets
3224								if (sectorData[sector_key].free > 0) {
3225									if (TicketGroup.countEnabled) {
3226										//add free/available tickets
3227										name += '<div><span class="tooltipTextLeft">' + js_lang.label_available_seats + ':</span><span class="tooltipTextRight">' + sectorData[sector_key].free + '</span></div>';
3228									}
3229									
3230									if (audit.showServiceCharge) {
3231										//prices with servic charge
3232										if (typeof sectorData[sector_key] !== 'undefined') {
3233											name += '<div class="tip-table">';										
3234											name += '<div><span class="tooltipTextLeft">' + js_lang.audit_price
3235													+ ':</span><span class="tooltipTextLeft">' + js_lang.basket_lang_basket_syscharge
3236													+ ':</span><span class="tooltipTextLeft">' + js_lang.audit_final_price + ':</span></div>';
3237											for (var price in sectorData[sector_key].pricecat_ids) {
3238												var service_charge = audit.basketManager.getServiceCharge(1, price);
3239												name += '<div><span class="tooltipTextRight">' + it_price_format(price) + '</span>'
3240													+ '<span class="tooltipTextRight">' + it_price_format(service_charge) + '</span>'
3241													+ '<span class="tooltipTextRight">' + it_price_format(parseFloat(price) + service_charge) + '</span></div>';
3242											}
3243											name += '</div>';
3244										}
3245									} else {
3246										//only ticket prices (interval for more prices)
3247										name += '<div><span class="tooltipTextLeft">' + js_lang.audit_price + ':</span> <span class="tooltipTextRight">' + it_price_format(sectorData[sector_key].pricemin);
3248										if (sectorData[sector_key].pricemax > sectorData[sector_key].pricemin) {
3249											name += ' - ' + it_price_format(sectorData[sector_key].pricemax);
3250										}
3251										name += '</span></div>';
3252									}
3253									
3254									//show special sector alert
3255									if (audit.hasSpecialSectors && audit.specialSectors.indexOf(sector_id) >= 0) {
3256										name += '<div>' + js_lang.label_sepcial_sector_tooltip + '</div>';
3257									}
3258									
3259									if (ProtectedSectors.enabled) {
3260										var protection = ProtectedSectors.isProtected(sector_id);
3261										if (protection !== false) {
3262											name += '<div>' + js_lang.label_audit_coupon_protected_sector.replace('%name%', protection) + '</div>';
3263										}
3264									}
3265									
3266									//add id row in #dev mode
3267									if (Seat.collectMode) {
3268										name += '<div><span class="tooltipTextLeft">SectorID:</span> <span class="tooltipTextRight">' + sector_id + '</span></div>';
3269									}
3270									
3271									sectorData[sector_key].tooltip = name;
3272									more = true;
3273								} else if (Seat.collectMode) {
3274									//add id row in #dev mode
3275									name += '<div><span class="tooltipTextLeft">SectorID:</span> <span class="tooltipTextRight">' + sector_id + '</span></div>';
3276								}
3277							} else {
3278								//is a graphic (fill type) sector
3279								name = '<div><span class="tooltipTextLeft">' + sectorData[sector_key].name + '</span></div>';
3280								
3281								//add id row in #dev mode
3282								if (Seat.collectMode) {
3283									name += '<div><span class="tooltipTextLeft">SectorID:</span> <span class="tooltipTextRight">' + sector_id + '</span></div>';
3284								}
3285							}
3286						} else {
3287							//sector tooltip already set
3288							name = sectorData[sector_key].tooltip;
3289							more = true;
3290						}
3291					} else {
3292						var sectorNode = $('#sector_' + sector_id);
3293						var name = sectorNode.attr('data-name');
3294						if (name !== undefined) {
3295							name = '<span class="tooltipTextLeft">' + js_lang.audit_sector + ':</span> <span class="tooltipTextRight">' + name + '</span>';
3296							if (Seat.collectMode) {
3297								name += '<div><span class="tooltipTextLeft">SectorID:</span> <span class="tooltipTextRight">' + sector_id + '</span></div>';
3298							}
3299						}
3300					}
3301					
3302					if (name !== undefined && name !== '') {
3303						if (
3304							//sector has html content
3305							typeof sector_html !== 'undefined'
3306							&& typeof sector_html[sector_id] !== 'undefined'
3307							//sector is free to use
3308							&& typeof sectorData[sector_key] !== 'undefined'
3309							&& sectorData[sector_key].free > 0
3310						) {
3311							//add html content - tooltip top and bottom contents (if is free)
3312							name = sector_html[sector_id].top + name + sector_html[sector_id].bottom;
3313						}
3314						
3315						popupdiv.html(name);
3316						popupdiv.show();
3317						if (!more) {
3318							popupdiv.addClass('simple-data');
3319						} else {
3320							popupdiv.removeClass('simple-data');
3321						}
3322						tooltipObj.setPosition();
3323					}
3324				}, tooltipObj.waitingMode ? 150 : 300);
3325			},
3326			
3327			'showinit': false,
3328			'showSeatsTooltip': function (j) {
3329				currentSeat = j;
3330				if (showTimer !== false) {
3331					clearTimeout(showTimer);
3332				}
3333				
3334				tooltipObj.showinit = true;
3335				showTimer = setTimeout(function() {
3336					tooltipObj.displayTooltip();
3337					tooltipObj.showinit = false;
3338				}, tooltipObj.waitingMode ? 150 : 250);
3339			},
3340			
3341			'displayTooltip': function() {
3342				showTimer = false;
3343				if (currentSeat !== false) {
3344					if (tooltipObj.waitingMode) {
3345						tooltipObj.clearDelayedHide();
3346					}
3347					
3348					var seat = currentSeat.seat;
3349					var row = currentSeat.row;
3350					var sector = currentSeat.sector;
3351					var seatId = seat.id.seatID,
3352						overSeatId = tooltipObj.over;
3353					tooltipObj.over = seatId;
3354					var seatElement = SeatingChart.getSeat(seatId);
3355					var seatName = seat.label.getText();
3356					var rowName = '';
3357					if (row) {
3358						if (row.labelLeft) {
3359							rowName = row.labelLeft.getText();
3360						} else if (row.labelRight) {
3361							rowName = row.labelRight.getText();
3362						}
3363					}
3364					var sectorName = sector ? sector.label.getText() : '';
3365					
3366					//best ticket sector setup
3367					var isBestTicketSector = false;
3368					if (audit.mlszEnabled && audit.mlszAuditObjectExists) {
3369						isBestTicketSector = MLSZAudit.isBestTicketSector(seat.id.sectorID, true);
3370					} else if (audit.bestTicketSectors.length > 0 && audit.bestTicketObjectExists) {
3371						isBestTicketSector = BestTicket.isBestTicketSector(seat.id.sectorID, true);
3372					}
3373					
3374					var obstructedView = seatElement.isOV;
3375					var is_wheelchair = seatElement.isWC;
3376					seatName = seatName.length ? seatName : '-';
3377					rowName = rowName.length ? rowName : '-';
3378					sectorName = sectorName.length ? sectorName : '-';
3379					var status = seatElement.status,
3380						price = seatElement.price,
3381						figureHtml = '';
3382
3383					if (audit.stagePictures) {
3384						var stageimage = StageImages.getItem(seatId);
3385						var stageImageParam = {
3386							'seatName': seatName,
3387							'rowName': rowName,
3388							'sectorName': sectorName
3389						};
3390						
3391						if (stageimage) {
3392							if (stageimage !== 'false') {
3393								figureHtml = tooltipObj.getStageImageHtml(
3394									$.extend({}, stageImageParam, {'StagePictureUrl': stageimage})
3395								);
3396							}
3397						} else {
3398							self.ajaxCallInProgress++;
3399							popupdiv.find('figure').remove();
3400							$.ajax({
3401								url: '/auditview/stage_picture/' + auditId + '/'+ seatId,
3402								type: 'get',
3403								dataType: 'json',
3404								success: function(data) {
3405									self.ajaxCallInProgress--;
3406									if (!data.errorcode && data.StagePictureUrl) {
3407										StageImages.setItem(seatId, data.StagePictureUrl)
3408										popupdiv.find('figure').remove();
3409										popupdiv
3410											.append(tooltipObj.getStageImageHtml($.extend({}, stageImageParam, {'StagePictureUrl': data.StagePictureUrl})))
3411											.find('figure img').one("load", function() {
3412												tooltipObj.setPosition();
3413											}).each(function() {
3414												if (this.complete) {
3415													$(this).load();
3416												}
3417											});
3418										tooltipObj.setPosition();
3419									} else {
3420										StageImages.setItem(seatId, 'false');
3421									}
3422								},
3423								error: function(jqXHR, textStatus, errorThrown) {
3424									self.ajaxCallInProgress--;
3425									j.data('stageimage', 'false');
3426								},
3427								cache: true
3428							});
3429						}
3430					}
3431					
3432					var showPrice = status == '0' || status == '1' || (isBestTicketSector && price > 0);
3433					var divHTML = '';
3434					
3435					if (audit.showServiceCharge && showPrice) {
3436						divHTML += '<div class="tip-title"><span class="tooltipTextLeft">' + js_lang.audit_location + ':</span>'; //todo: add js_lang.audit_position
3437						if (is_wheelchair) {
3438							divHTML += '<img src="' + Config.wheelchair_seat_icon + '" style="width: 40px; height: 40px; float: right; background-color: ' + Config.wheelchair_seat_bgcolor + ';" />';
3439						}
3440						var positionArr = [];
3441						var pattern = this.sitelang === 'hu' ? '%name%.&nbsp;%level%' : '%level%&nbsp;%name%';
3442						if (sectorName !== '-') {
3443							positionArr.push(sectorName);
3444						}
3445						if (rowName !== '-') {
3446							positionArr.push(pattern.replace('%name%', rowName).replace('%level%', js_lang.audit_row));
3447						}
3448						if (seatName !== '-') {
3449							positionArr.push(pattern.replace('%name%', seatName).replace('%level%', js_lang.audit_seat));
3450						}
3451						divHTML += '<span class="tooltipTextRight">' + (positionArr.length ? positionArr.join(', ') : '-') + '</span></div>';
3452					} else {
3453						divHTML += '<div><span class="tooltipTextLeft">
3453' + js_lang.audit_seat + ':</span>';
3454						if (is_wheelchair) {
3455							divHTML += '<img src="' + Config.wheelchair_seat_icon + '" style="width: 40px; height: 40px; float: right; background-color: ' + Config.wheelchair_seat_bgcolor + ';" />';
3456						}
3457						divHTML += '<span class="tooltipTextRight">' + seatName + '</span></div>'
3458							+ '<div><span class="tooltipTextLeft">' + js_lang.audit_row + ':</span>'
3459							+ '<span class="tooltipTextRight">' + rowName  + '</span></div>'
3460							+ '<div><span class="tooltipTextLeft">' + js_lang.audit_sector + ':</span>'
3461							+ '<span class="tooltipTextRight">' +  sectorName + '</span></div>';
3462					}
3463					
3464					if (showPrice) {
3465						if (typeof category_names !== 'undefined') {
3466							var sectorID = seat.id.sectorID;
3467							if (typeof category_names[sectorID] !== 'undefined' && typeof category_names[sectorID][price] !== 'undefined') {
3468								divHTML += '<div><span class="tooltipTextLeft">' + js_lang.label_ticket_category + ":</span>"
3469									+ '<span class="tooltipTextRight" id="tooltipTextRightPrice">' + category_names[sectorID][price] + '</span></div>';
3470							}
3471						}
3472						
3473						if (audit.showServiceCharge && typeof TicketGroup.list[price] !== 'undefined') {
3474							divHTML += '<div><span class="tooltipTextLeft">' + js_lang.audit_price + ":</span>"
3475								+ '<span class="tooltipTextRight">' + it_price_format(price)  + '</span></div>'
3476								+ '<div><span class="tooltipTextLeft">' + js_lang.basket_lang_basket_syscharge + ":</span>"
3477								+ '<span class="tooltipTextRight">' + TicketGroup.list[price].service_charge_str + '</span></div>'
3478								+ '<div class="tip-total"><span class="tooltipTextLeft">' + js_lang.audit_final_price + ":</span>"
3479								+ '<span class="tooltipTextRight" id="tooltipTextRightPrice">' + TicketGroup.list[price].sum_price_str + '</span></div>';
3480						} else {
3481							divHTML += '<div><span class="tooltipTextLeft">' + js_lang.audit_price + ":</span>"
3482								+ '<span class="tooltipTextRight" id="tooltipTextRightPrice">' + it_price_format(price) + '</span></div>';
3483						}
3484					}
3485					
3486					if (obstructedView) {
3487						divHTML +=  "<div class=\"tip-warning\"><span class=\"tooltipTextLeft tooltipObstructedView\">" + js_lang.audit_seat_obstructed_view + "</span></div>";
3488					}
3489					
3490					//show special sector alert
3491					if (audit.hasSpecialSectors && audit.specialSectors.indexOf(seat.id.sectorID) >= 0) {
3492						divHTML += '<div>' + js_lang.label_sepcial_sector_tooltip + '</div>';
3493					}
3494					
3495					if (ProtectedSectors.enabled) {
3496						var protection = ProtectedSectors.isProtected(seat.id.sectorID);
3497						if (protection !== false) {
3498							divHTML += '<div>' + js_lang.label_audit_coupon_protected_sector.replace('%name%', protection) + '</div>';
3499						}
3500					}
3501					
3502					//add id row in #dev mode
3503					if (Seat.collectMode) {
3504						divHTML += '<div><span class="tooltipTextLeft">SectorID:</span> <span class="tooltipTextRight">' + seat.id.sectorID + '</span></div>'
3505							+ '<div><span class="tooltipTextLeft">SeatID:</span> <span class="tooltipTextRight">' + seat.id.seatID + '</span></div>';
3506						var seatobj = SeatingChart.getSeat(seat.id.seatID);
3507						divHTML += '<div><span class="tooltipTextLeft">TicketID:</span> <span class="tooltipTextRight">' + seatobj.ticketID + '</span></div>'
3508							+ '<div><span class="tooltipTextLeft">PriceCatID:</span> <span class="tooltipTextRight">' + seatobj.pricecatID + '</span></div>';
3509					}
3510					
3511					
3512					//sector has html content
3513					if (typeof seat_html !== 'undefined' && typeof seat_html[seat.id.sectorID] !== 'undefined') {
3514						//add html content - tooltip bottom contents
3515						divHTML += seat_html[seat.id.sectorID];
3516					}
3517					
3518					divHTML += "</div>";
3519					divHTML += figureHtml;
3520					
3521					if (this.waitingMode) {
3522						divHTML += '<div id="waitinghoverfix" style="position:absolute; width:16px; height:20px;"></div>';
3523					}
3524					
3525					popupdiv.html(divHTML);
3526					popupdiv.show();
3527					popupdiv.removeClass('simple-data');
3528					tooltipObj.setPosition();
3529				} else {
3530					//no current seat - force hide tooltip
3531					tooltipObj.hide();
3532				}
3533			},
3534			
3535			'initMouse': function() {
3536				$('#audit').off('mousemove').on('mousemove', function(e) {
3537					mousePos = {
3538						'X': e.pageX,
3539						'Y': e.pageY
3540					};
3541					if (!tooltipObj.waitingMode && popupdiv.is(':visible')) {
3542						tooltipObj.setPosition(true);
3543					}
3544				});
3545			},
3546			
3547			'getStageImageHtml': function(data) {
3548				if (!this.stageImage360) {
3549					//standard image
3550					return '<figure>'
3551						+ '<a class="preview" href="#"><img src="' + data.StagePictureUrl + '" alt="'
3552						+ js_lang.audit_seat + ': ' + data.seatName + ' / '
3553						+ js_lang.audit_row + ': ' + data.rowName + ' / '
3554						+ js_lang.audit_sector + ': ' + data.sectorName
3555						+ '" /></a>'
3556						+ '<figcaption>' + js_lang.audit_tooltip_picture_disclaimer + '</figcaption>'
3557						+ '</figure>';
3558				} else {
3559					//rotate picture
3560					var imgwidth = Config.stageImageWidth;
3561					var imgheight = Config.stageImageHeight;
3562					if (!imgwidth || !imgheight) {
3563						imgwidth = 320;
3564						imgheight = 160;
3565					}
3566					
3567					return '<figure>'
3568						+ '<div id="panorama" style="width:'+imgwidth+'px;height:'+imgheight+'px;"></div>'
3569						+ '<figcaption>' + js_lang.audit_tooltip_picture_disclaimer + '</figcaption>'
3570						+ '</figure>'
3571						+ '<script>pannellum.viewer("panorama", {"type": "equirectangular", "panorama": "' + data.StagePictureUrl + '", "autoLoad":true});</script>';
3572				}
3573			},
3574			
3575			'mouseout': function() {
3576				tooltipObj.hide();
3577			},
3578			'hide': function() {
3579				if (showTimer !== false) {
3580					clearTimeout(showTimer);
3581					showTimer = false;
3582				}
3583				currentSeat = false;
3584				popupdiv.css('display', 'none');
3585				popupdiv.empty();
3586				tooltipObj.over = false;
3587			},
3588			'delayedHide': function() {
3589				if (delayTimer === false) {
3590					delayTimer = setTimeout(function() {
3591						tooltipObj.delayTimer = false;
3592						tooltipObj.hide();
3593					}, 500);
3594				}
3595			},
3596			'clearDelayedHide': function() {
3597				if (delayTimer !== false) {
3598					clearTimeout(delayTimer);
3599					delayTimer = false;
3600				}
3601			},
3602			'setPosition': function(force) {
3603				var calcPos = tooltipObj.calculatePosition(tooltipObj.arrowOrientation);
3604					recalculate = false,
3605					arrowX = tooltipObj.arrowOrientation.X = popupdiv.hasClass('arrowright') ? 'arrowright' : 'arrowleft',
3606					arrowY = tooltipObj.arrowOrientation.Y = popupdiv.hasClass('arrowtop') ? 'arrowtop' : popupdiv.hasClass('arrowbottom') ? 'arrowbottom' : 'arrowmiddle',
3607					borderLimit = 30;
3608
3609				if (!force && arrowX == 'arrowleft' && calcPos.left + popupdiv.outerWidth() >= calcPos.auditWidth - borderLimit) {
3610					arrowX = 'arrowright';
3611				} else if (!force && arrowX == 'arrowright' && calcPos.left <= borderLimit) {
3612					arrowX = 'arrowleft';
3613				}
3614				if (arrowY != 'arrowmiddle' && mousePos.Y - popupdiv.outerHeight() / 2 > calcPos.auditTop + borderLimit && mousePos.Y  + popupdiv.height() / 2 < calcPos.auditTop + calcPos.auditHeight - borderLimit) {
3615					arrowY = 'arrowmiddle';
3616				} else if (arrowY != 'arrowtop' && mousePos.Y- popupdiv.outerHeight() / 2 < calcPos.auditTop) {
3617					arrowY = 'arrowtop';
3618				} else if (arrowY != 'arrowbottom' && mousePos.Y  + popupdiv.outerHeight() / 2 > calcPos.auditTop + calcPos.auditHeight) {
3619					arrowY = 'arrowbottom';
3620				}
3621				if (tooltipObj.arrowOrientation.X !== arrowX || tooltipObj.arrowOrientation.Y !== arrowY) {
3622					popupdiv.removeClass();
3623					tooltipObj.arrowOrientation = {
3624						'X': arrowX,
3625						'Y': arrowY
3626					};
3627					calcPos = tooltipObj.calculatePosition(tooltipObj.arrowOrientation);
3628				}
3629				popupdiv
3630					.css({
3631						left: calcPos.left,
3632						top: calcPos.top
3633					})
3634					.addClass(tooltipObj.arrowOrientation.X + ' ' + tooltipObj.arrowOrientation.Y);
3635				
3636				if (this.waitingMode) {
3637					this.waitingHoverFix(tooltipObj.arrowOrientation);
3638				}
3639			},
3640			'calculatePosition': function(arrowOrientation) {
3641				var auditObj = $('#' + Consts.domIds.audit),
3642					auditPos = auditObj.offset(),
3643					result = {
3644						'auditTop': auditPos.top,
3645						'auditLeft': auditPos.left,
3646						'auditHeight': auditObj.outerHeight(),
3647						'auditWidth': auditObj.outerWidth()
3648					},
3649					fineTune = {
3650						'arrowright': [-15, 0],
3651						'arrowleft': [20, 0],
3652						'arrowtop': [10, -10],
3653						'arrowbottom': [10, 10],
3654						'arrowmiddle': [10, 0]
3655					};
3656
3657				switch(arrowOrientation.X) {
3658					case 'arrowright':
3659						result = $.extend({}, result, {
3660								'left': mousePos.X - popupdiv.outerWidth() - auditPos.left + fineTune[arrowOrientation.X][0]
3661							}
3662						);
3663						break;
3664					case 'arrowleft':
3665					default:
3666						result = $.extend({}, result, {
3667								'left': mousePos.X - auditPos.left + fineTune[arrowOrientation.X][0]
3668							}
3669						);
3670				}
3671				switch(arrowOrientation.Y) {
3672					case 'arrowtop':
3673						result = $.extend({}, result, {
3674								'top': mousePos.Y - auditPos.top + fineTune[arrowOrientation.Y][1]
3675							}
3676						);
3677						break;
3678					case 'arrowbottom':
3679						result = $.extend({}, result, {
3680								'top': mousePos.Y - auditPos.top - popupdiv.outerHeight() + fineTune[arrowOrientation.Y][1]
3681							}
3682						);
3683						break;
3684					case 'arrowmiddle':
3685					default:
3686						result = $.extend({}, result, {
3687								'top': mousePos.Y - auditPos.top - popupdiv.outerHeight() / 2 + fineTune[arrowOrientation.Y][1]
3688							}
3689						);
3690				}
3691				return result;
3692			},
3693			'waitingHoverFix': function(arrowOrientation) {
3694				var css = {
3695					'left': 'none',
3696					'right': 'none',
3697					'top': 'none',
3698					'bottom': 'none'
3699				};
3700				
3701				//horizontal position
3702				if (arrowOrientation.X === 'arrowright') {
3703					css.right = "-14px";
3704				} else if (arrowOrientation.X === 'arrowleft') {
3705					css.left = "-14px";
3706				}
3707				
3708				//vertical position
3709				if (arrowOrientation.Y === 'arrowtop') {
3710					css.top = '0';
3711				} else if (arrowOrientation.Y === 'arrowbottom') {
3712					css.bottom = '0';
3713				} else if (arrowOrientation.Y === 'arrowmiddle') {
3714					css.top = Math.round(popupdiv.outerHeight() / 2) - 12 + 'px';
3715				}
3716				
3717				$('div#waitinghoverfix').css(css);
3718			}
3719		};
3720		
3721	tooltipObj.initMouse();
3722	return tooltipObj;
3723}
3724
3725//store loaded stage images
3726var StageImages = {
3727	//list of items
3728	list: {},
3729	
3730	//get one item
3731	getItem: function(seat_id) {
3732		if (typeof this.list[seat_id]) {
3733			return this.list[seat_id];
3734		}
3735		return null;
3736	},
3737	
3738	//update an item
3739	setItem: function(seat_id, value) {
3740		this.list[seat_id] = value;
3741	}
3742};
3743
3744var SeatingChart = {
3745	//additional data of loaded seats
3746	seatList: {},
3747	seatOrderList: [],
3748	
3749	//ticket count fix for special sectors (multiple select/deselect)
3750	counterFix: {
3751		active: false,
3752		sectorID: 0,
3753		price: 0,
3754		initCount: 0,
3755		seatIDs: []
3756	},
3757	
3758	//return addition data of a loaded seat - by seatID
3759	getSeat: function(seatID) {
3760		if (typeof this.seatList[seatID] !== 'undefined') {
3761			return this.seatList[seatID];
3762		}
3763		return false;
3764	},
3765	
3766	//return seats data from a sector
3767	getSectorSeats: function(sectorID) {
3768		var list = [];
3769		for(var seatID in this.seatList) {
3770			if (this.seatList[seatID].sectorID === sectorID) {
3771				list.push({
3772					'id': seatID,
3773					'status': this.seatList[seatID].status
3774				});
3775			}
3776		}
3777		return list;
3778	},
3779	
3780	//update status of a loaded seat
3781	updateSeatStatus: function(seatID, status, update_viewer, group_id) {
3782		seatID = parseInt(seatID, 10);
3783		if (typeof this.seatList[seatID] !== 'undefined') {
3784			this.seatList[seatID].status = status;
3785			var params = {
3786				seat_id: seatID,
3787				status: status
3788			};
3789			if (typeof update_viewer !== 'undefined' && update_viewer) {
3790				params.update_viewer = true;
3791				if (typeof group_id !== 'undefined' && group_id !== false) {
3792					params.group_id = group_id;
3793				}
3794			}
3795			var updateSeatStatusEvent = new CustomEvent("updateSeatStatusEvent", { detail: params });
3796			document.dispatchEvent(updateSeatStatusEvent);
3797		}
3798	},
3799	
3800	//update seat price in dynamic price mode (engough here, sync to seat-viewer not needed)
3801	updateSeatPrice: function(pricecatList, sectors) {
3802		$.each(SeatingChart.seatList, function(seatID, seat) {
3803			if (seat.pricecatID in pricecatList && SeatingChart.seatList[seatID].price != pricecatList[seat.pricecatID]) {
3804				//price changed for current PriceCat
3805				SeatingChart.seatList[seatID].price = pricecatList[seat.pricecatID];
3806			}
3807		});
3808		
3809		var data = {
3810			sectorGroups: {},
3811			mapColors: {},
3812			pricecats: {}
3813		};
3814		
3815		$.each(sectors, function(sid, sector) {
3816			data.sectorGroups[sid] = {
3817				'groupcolor': Math.min(sector.groups)
3818			};
3819			data.mapColors[sid] = sector.groupcol;
3820		});
3821		
3822		$.each(pricecatList, function(pcid, price) {
3823			data.pricecats[pcid] = {
3824				'price': price,
3825				'group': price in TicketGroup.list ? TicketGroup.list[price].index : TicketGroup.getGroupIndexByPrice(price)
3826			};
3827		});
3828		
3829		var dynamicUpdateGroupsEvent = new CustomEvent("dynamicUpdateGroups", {
3830			detail: data
3831		});
3832		document.dispatchEvent(dynamicUpdateGroupsEvent);
3833	},
3834	
3835	//return true if sector has selection
3836	sectorHasSelection: function(sectorID) {
3837		for(var seatID in this.seatList) {
3838			if (this.seatList[seatID].status === 1 && this.seatList[seatID].sectorID === sectorID) {
3839				//selection found in sector
3840				return true;
3841			}
3842		}
3843		
3844		//no selection found in the sector
3845		return false;
3846	},
3847	
3848	//on special sectors (auto multiple seat selection) need to fix available seat counter
3849	initCountFix: function(seat_id) {
3850		if (seat_id && !this.counterFix.active) {
3851			var seat = this.getSeat(seat_id);
3852			if (seat) {
3853				var sector_id = seat.sectorID;
3854				var seat_ids = [];
3855				var selected = 0;
3856				var price = seat.price;
3857				$.each(this.seatList, function(sid, seat) {
3858					if (seat.sectorID === sector_id) {
3859						seat_ids.push(sid);
3860						if (seat.status === 1) {
3861							selected++;
3862						}
3863					}
3864				});
3865				
3866				this.counterFix.active = true;
3867				this.counterFix.sectorID = sector_id;
3868				this.counterFix.price = price;
3869				this.counterFix.initCount = selected;
3870				this.counterFix.seatIDs = seat_ids;
3871			}
3872		}
3873	},
3874	
3875	//finalize available ticket counter on multiple selection (special sector)
3876	finishCountFix: function() {
3877		if (this.counterFix.active) {
3878			var selected = 0;
3879			for(var i=0; i<this.counterFix.seatIDs.length; i++) {
3880				var seat = this.getSeat(this.counterFix.seatIDs[i]);
3881				if (seat.status === 1) {
3882					selected++;
3883				}
3884			}
3885			var diff = this.counterFix.initCount - selected;
3886			if (diff !== 0) {
3887				TicketGroup.changeTicketCounter(this.counterFix.price, diff);
3888			}
3889			
3890			//reset counter fix
3891			this.counterFix.active = false;
3892			this.counterFix.sectorID = 0;
3893			this.counterFix.price = 0;
3894			this.counterFix.initCount = 0;
3895			this.counterFix.seatIDs = [];
3896		}
3897	}
3898};
3899
3900//minimap setup object
3901var MiniMAP = {
3902	initialized: false,
3903	
3904	//initialize minimap
3905	init: function() {
3906		//initialize once
3907		if (this.initialized) {
3908			return false;
3909		}
3910		this.initialized = true;
3911		
3912		//check minimap status (enabled/disabled)
3913		if (typeof ticketType !== 'undefined' && ticketType === 'freecombined') {
3914			//minimap disabled for free combined season ticket
3915			Config.minimap.enabled = false;
3916		}
3917		if (!Config.minimap.enabled) {
3918			return false;
3919		}
3920		
3921		//check minimap html dom object
3922		this.checkMinimapObject();
3923		
3924		//add triggers
3925		var minimap = $('div#minimap');
3926		
3927		if (!$(minimap).is(':visible')) {
3928			//disable minimap when minimap object is not visible (on start)
3929			document.dispatchEvent(new CustomEvent("blockMinimap", {
3930				detail: true
3931			}));
3932		}
3933		$(window).resize(function() {
3934			//disable minimap when minimap object is not visible (on window resize)
3935			document.dispatchEvent(new CustomEvent("blockMinimap", {
3936				detail: !$(minimap).is(':visible')
3937			}));
3938		});
3939
3940		return true;
3941	},
3942	
3943	//check minimap dom object and create if not exists
3944	checkMinimapObject: function() {
3945		if (Config.minimap.enabled && !$('div#minimap').size()) {
3946			var minimapObj = $('<div id="minimap"></div>').css({
3947				"display": "block",
3948				"position": "absolute",
3949				"width": Config.minimap.size[0] + "px",
3950				"height": Config.minimap.size[1] + "px",
3951				"right": Config.minimap.offset[0] + "px",
3952				"bottom": Config.minimap.offset[1] + "px",
3953				"border": "solid 1px " + Config.minimap.border,
3954				"background-color": Config.minimap.background,
3955				"z-index": 5
3956			});
3957			
3958			$('div#' + Consts.domIds.audit).append(minimapObj);
3959			minimapFound = true;
3960		}
3961	}
3962};
3963
3964//Protected sectors with coupon codes
3965var ProtectedSectors = {
3966	enabled: false,
3967	filterEnabled: false,
3968	
3969	sector: null,
3970	name: null,
3971	used: null,
3972	customLabels: null,
3973	couponInputObj: null,
3974	pinInputObj: null,
3975	
3976	//intialize
3977	init: function() {
3978		this.sector = {};
3979		this.name = {};
3980		this.used = [];
3981		this.customLabels = {};
3982		
3983		if (typeof protected_sectors !== 'undefined') {
3984			$.each(protected_sectors, function(protection_id, protection) {
3985				ProtectedSectors.enabled = true;
3986				ProtectedSectors.name[protection_id] = protection.name;
3987				for(var i=0; i<protection.sectors.length; i++) {
3988					ProtectedSectors.sector[protection.sectors[i]] = protection_id;
3989				}
3990				if (protection.title || protection.info) {
3991					ProtectedSectors.customLabels[protection_id] = {
3992						"title": protection.title,
3993						"info": protection.info
3994					};
3995				}
3996			});
3997		}
3998		
3999		if (typeof used_permissions !== 'undefined') {
4000			for(var i=0; i<used_permissions.length; i++) {
4001				if (typeof ProtectedSectors.name[used_permissions[i]] !== 'undefined') {
4002					ProtectedSectors.used.push(used_permissions[i].toString());
4003				}
4004			}
4005		}
4006		
4007		$(document).on('focus', 'input.pretected_sector_couponcode', function() {
4008			ProtectedSectors.couponInputObj = $(this);
4009		});
4010		
4011		$(document).on('focus', 'input.pretected_sector_pin_code', function() {
4012			ProtectedSectors.pinInputObj = $(this);
4013		});
4014		
4015		//add coupon code filters
4016		if (ProtectedSectors.enabled && typeof Config.showCouponFilter !== 'undefined' && Config.showCouponFilter) {
4017			ProtectedSectors.addFilters();
4018			if (this.used.length > 0) {
4019				for (var i=0; i<this.used.length; i++) {
4020					this.markActivatedFilter(this.used[i]);
4021				}
4022			}
4023		}
4024	},
4025	
4026	//check if a sector is protected and return protecion name
4027	isProtected: function(sector_id) {
4028		var protection_id = typeof this.sector[sector_id] !== 'undefined' ? this.sector[sector_id] : 0;
4029		if (protection_id) {
4030			return this.name[protection_id];
4031		}
4032		return false;
4033	},
4034	
4035	//seat selection require coupon code
4036	codeRequired: function(sector_id) {
4037		var protection_id = typeof this.sector[sector_id] !== 'undefined' ? this.sector[sector_id] : 0;
4038		if (protection_id > 0 && this.used.indexOf(protection_id) < 0) {
4039			//sector is protected and coupon code not used yet
4040			return true;
4041		}
4042		return false;
4043	},
4044	
4045	getPopupText: function(sector_id) {
4046		var data = {
4047			'title': '',
4048			'info': ''
4049		};
4050		var protection_id = typeof this.sector[sector_id] !== 'undefined' ? this.sector[sector_id] : 0;
4051		if (protection_id && typeof this.customLabels[protection_id] !== 'undefined') {
4052			data.title = this.customLabels[protection_id].title;
4053			data.info = this.customLabels[protection_id].info;
4054		}
4055		if (!data.title) {
4056			data.title = js_lang.label_audit_coupon_protected_sector_title;
4057		}
4058		if (!data.info) {
4059			var protection_name = this.isProtected(sector_id);
4060			if (protection_name === false) {
4061				protection_name = '';
4062			}
4063			data.info = js_lang.label_audit_coupon_protected_sector.replace("%name%", protection_name);
4064		}
4065		return data;
4066	},
4067	
4068	//show coupon code popup
4069	popup: function(seat) {
4070		var text = this.getPopupText(seat.sector_id);
4071		if (mlsz_coupon_is_clubcard) {
4072			var popup_message = text.info + '<input type="text" class="pretected_sector_couponcode" placeholder="' + js_lang.label_fancard_number + '" />'
4073			+ '<div class="input-group">'
4074			+ '<input type="password" class="pretected_sector_pin_code input-group-field pw-eye-fld" placeholder="' + js_lang.pin_code + '" />'
4075			+ '<span class="input-group-label pw-eye-btn"><i class="fa fa-eye-slash"></i></span>'
4076			+ '</div>';
4077		} else {
4078			var popup_message = text.info + '<input type="text" class="pretected_sector_couponcode" placeholder="' + js_lang.admin_coupon_code + '" />';
4079		}
4080		
4081		//show popup
4082		revDialog({
4083			title: text.title,
4084			content: popup_message,
4085			leftButton: {
4086				label: js_lang.label_audit_coupon_send,
4087				onClick: function(obj) {
4088					var popupObj = $(obj.currentTarget).parents('div.revdialog_body'); //standard popup
4089					var inputObj = popupObj.length > 0 ? $('input.pretected_sector_couponcode', popupObj[0]) : ProtectedSectors.couponInputObj; //use last focused input if standard popup fails
4090					var couponcode = inputObj && inputObj.length > 0 ? $.trim($(inputObj).val()) : '';
4091					var inputObj2 = popupObj.length > 0 ? $('input.pretected_sector_pin_ccode', popupObj[0]) : ProtectedSectors.pinInputObj; //use last focused input if standard popup fails
4092					var pinCode = inputObj2 && inputObj2.length > 0 ? $.trim($(inputObj2).val()) : '';
4093					ProtectedSectors.checkCouponCode(seat.sector_id, couponcode, seat, pinCode);
4094				},
4095				close: true
4096			},
4097			rightButton: {
4098				label: js_lang.label_audit_coupon_eject,
4099				class: 'dark',
4100				onClick: function() {
4101					ProtectedSectors.closeProtectedSectors(seat.sector_id);
4102				},
4103				close: true
4104			}
4105		});
4106		
4107		//focus to input field
4108		setTimeout(function() {
4109			var inputObj = $('input.pretected_sector_couponcode:visible')[0];
4110			if (inputObj && $(inputObj).length === 1) {
4111				$(inputObj).focus();
4112			}
4113		}, 100);
4114	},
4115	
4116	//check a coupon code
4117	checkCouponCode: function(sector_id, couponcode, seat, pinCode) {
4118		var url = false;
4119		var params = {
4120			"sector_id": sector_id,
4121			"couponcode": couponcode,
4122			"pin_code": pinCode
4123		};
4124		if (audit.ticketType === 'event') {
4125			url = '/auditview/check_coupon';
4126			params.event_id = audit.eventId;
4127		} else if (audit.ticketType === 'seasonticket') {
4128			//todo for season tickets
4129		} else {
4130			//other types: reservation, seasonpackage
4131		}
4132		
4133		if (url !== false) {
4134			$.ajax({
4135				url: url,
4136				type: 'get',
4137				dataType: 'json',
4138				data: params,
4139				success: function(data, textStatus, jqXHR) {
4140					if (typeof data.success !== 'undefined' && data.success) {
4141						ProtectedSectors.checkCouponCodeResult(seat, true, '');
4142					} else {
4143						var message = typeof data.message !== 'undefined' ? data.message : '';
4144						ProtectedSectors.checkCouponCodeResult(null, false, message);
4145					}
4146				},
4147				error: function(jqXHR, textStatus, errorThrown) {
4148					ProtectedSectors.checkCouponCodeResult(null, false, '');
4149				},
4150				cache: false
4151			});
4152		}
4153	},
4154	
4155	//process coupon code checking result - show error or select seat on success
4156	checkCouponCodeResult: function(seat, success, message) {
4157		if (success) {
4158			//coupon code accepted - select seat
4159			var protection_id = typeof this.sector[seat.sector_id] !== 'undefined' ? this.sector[seat.sector_id] : 0;
4160			if (protection_id && seat) {
4161				this.used.push(protection_id);
4162				if (typeof seat.capacity === 'undefined') {
4163					//select seat
4164					document.dispatchEvent(new CustomEvent("seatForceSelect", {
4165						detail: {
4166							'seat': seat
4167						}
4168					}));
4169				} else {
4170					//capacity sector
4171					audit.openCapacityPopup(seat.sector_id);
4172				}
4173				
4174				if (this.filterEnabled) {
4175					this.markActivatedFilter(protection_id);
4176				}
4177			}
4178		} else {
4179			//bad coupon code
4180			if (message === '') {
4181				message = js_lang.invlid_coupon_code;
4182			}
4183			revDialog({
4184				title: js_lang.audit_error_popup_title,
4185				content: message,
4186				rightButton: {
4187					label: js_lang.admin_bmit,
4188					close: true,
4189					class: 'dark'
4190				}
4191			});
4192		}
4193	},
4194	
4195	//mark protected sectors as unavailable
4196	closeProtectedSectors: function(sector_id) {
4197		var protection_id = typeof this.sector[sector_id] !== 'undefined' ? this.sector[sector_id] : 0;
4198		var sectors = [];
4199		if (protection_id) {
4200			for(var i in this.sector) {
4201				if (this.sector[i] === protection_id) {
4202					sectors.push(parseInt(i, 10));
4203				}
4204			}
4205			document.dispatchEvent(new CustomEvent("closeSectors", {
4206				detail: {
4207					'sectors': sectors
4208				}
4209			}));
4210		}
4211	},
4212	
4213	//try to add coupon filters
4214	addFilters: function() {
4215		var parent = $('div.usagepart');
4216		
4217		if ($(parent).length === 0) {
4218			var tc = $('div.ticket-categories');
4219			if ($(tc).length === 1) {
4220				parent = $('<div>').attr('id', 'sector-filters');
4221				$(parent).insertAfter(tc);
4222			}
4223		}
4224		
4225		if ($(parent).length === 1) {
4226			this.filterEnabled = true;
4227			var filterObj = $('<div>').html('<h4>' + js_lang.label_coupon_sector_title + '</h4><ul id="coupon-filters"></ul>');
4228			$(parent).append(filterObj);
4229			var item_class = audit.mlszEnabled ? 'soldout' : 'seat-soldout';
4230			
4231			for(var permission_id in this.name) {
4232				var item = $('<li>').addClass(item_class).text(this.name[permission_id]).attr('id', 'secperm' + permission_id);
4233				var sector_ids = [];
4234				for(var sector_id in this.sector) {
4235					if (this.sector[sector_id] == permission_id) {
4236						sector_ids.push(sector_id);
4237					}
4238				}
4239				item.attr('data-sectors', sector_ids.join(','));
4240				$('ul#coupon-filters').append(item);
4241			}
4242			
4243			$(document).on('mouseenter', 'ul#coupon-filters > li', function() {
4244				$(this).addClass('highlighted');
4245				ProtectedSectors.updateFilters();
4246			});
4247			$(document).on('mouseleave', 'ul#coupon-filters > li', function() {
4248				if (!$(this).hasClass('hold')) {
4249					$(this).removeClass('highlighted');
4250					ProtectedSectors.updateFilters();
4251				}
4252			});
4253			$(document).on('click', 'ul#coupon-filters > li', function() {
4254				if ($(this).hasClass('hold')) {
4255					$(this).removeClass('hold').removeClass('highlighted');
4256				} else {
4257					$(this).addClass('hold').addClass('highlighted');
4258				}
4259				ProtectedSectors.updateFilters();
4260			});
4261		}
4262	},
4263	
4264	markActivatedFilter: function(permission_id) {
4265		$('li#secperm' + permission_id).attr('class', '');
4266		$('li#secperm' + permission_id).addClass(audit.mlszEnabled ? 'taken' : 'seat-available');
4267	},
4268	
4269	//update sector filters for price highlights
4270	updateFilters: function() {
4271		var filteredSectors = [];
4272		$('ul#coupon-filters li.highlighted').each(function() {
4273			var sectors = $(this).attr('data-sectors').split(',');
4274			for(var i=0; i<sectors.length; i++) {
4275				var sector_id = parseInt(sectors[i], 10);
4276				if (sector_id > 0 && filteredSectors.indexOf(sector_id) === -1) {
4277					filteredSectors.push(sector_id);
4278				}
4279			}
4280		});
4281		if (TicketGroup.filteredSectors.join(',') !== filteredSectors.join(',')) {
4282			TicketGroup.filteredSectors = filteredSectors;
4283			TicketGroup.updateFilter();
4284		}
4285	}
4286};
4287
4288//seat selection test
4289var Testing = {
4290	enabled: false,
4291	sectorsDiv: false,
4292	seatsDiv: false,
4293	sectorLoading: 0,
4294	
4295	//create objects
4296	init: function() {
4297		//initialize custom event listeners
4298		if (this.seatsDiv === false && this.sectorsDiv === false) {
4299			//update seat list after sector selection
4300			document.addEventListener("seatListToTest", function (e) {
4301				if (typeof e.detail.list !== 'undefined') {
4302					Testing.builidSeatList(e.detail.list);
4303				}
4304			});
4305		}
4306		
4307		//initialize html dom objects
4308		if (this.sectorsDiv === false) {
4309			this.sectorsDiv = $('<div>').attr('id', 'testing-sectors').attr('data-loading', 'true');
4310			$(this.sectorsDiv).attr('style', 'position:fixed; width:400px; height:120px; top:10px; left:10px; background-color:white; border:1px solid black; padding:5px 10px; overflow:auto; z-index: 10001;');
4311			$('body').append(this.sectorsDiv);
4312		}
4313		if (this.seatsDiv === false) {
4314			this.seatsDiv = $('<div>').attr('id', 'testing-seats').attr('data-loading', 'false').attr('data-sector', '0');
4315			$(this.seatsDiv).attr('style', 'position:fixed; width:400px; height:120px; top:10px; left:450px; background-color:white; border:1px solid black; padding:5px 10px; overflow:auto; z-index: 10001;');
4316			$('body').append(this.seatsDiv);
4317		}
4318		
4319		//add ID for basket item
4320		var basketObj = $('a.mini-basket')[0];
4321		if (!$(basketObj).attr('id')) {
4322			$(basketObj).attr('id', 'mini-basket-link');
4323		}
4324	},
4325	
4326	//tesing setup and intializing
4327	start: function() {
4328		this.enabled = true;
4329		this.init();
4330		
4331		$(this.sectorsDiv).on('click', 'a', function() {
4332			if (!$('div#loading').is(':visible')) {
4333				Testing.mark(this);
4334				Testing.clickSector($(this).attr('data-id'));
4335			}
4336		});
4337		$(this.seatsDiv).on('click', 'a', function() {
4338			if (!$('div#loading').is(':visible')) {
4339				Testing.mark(this);
4340				Testing.clickSeat($(this).attr('data-id'));
4341			}
4342		});
4343	},
4344	
4345	//create a link list of available sectors
4346	buildSectorList: function(sectorInitialInfos) {
4347		var best = audit.bestTicketSectors;
4348		$(this.sectorsDiv).html('');
4349		for(var id in sectorInitialInfos) {
4350			if (sectorInitialInfos[id].isFree === 1 && sectorInitialInfos[id].extraSector === 'N') {
4351				var isBest = best.indexOf(parseInt(id, 10)) > -1;
4352				var name = 'SECTOR' + id;
4353				var link = $('<a>').attr('id', 'testing-sector-' + id).attr('href', '#test').attr('data-id', id).attr('data-best', isBest ? 'true' : 'false');
4354				if (isBest) {
4355					name = 'BEST' + name;
4356				}
4357				$(link).text('[' + name + ']');
4358				$(this.sectorsDiv).append(link);
4359			}
4360		}
4361		$(this.sectorsDiv).attr('data-loading', 'false');
4362	},
4363	
4364	//create a link list of available seats
4365	builidSeatList: function(seatIDList) {
4366		$(this.seatsDiv).html('').attr('data-sector', this.sectorLoading);
4367		for(var i=0; i<seatIDList.length; i++) {
4368			var id = seatIDList[i];
4369			var link = $('<a>').attr('id', 'testing-seat-' + id).attr('href', '#test').text('[' + id + ']').attr('data-id', id);
4370			$(this.seatsDiv).append(link);
4371		}
4372		$(this.seatsDiv).attr('data-loading', 'false');
4373		this.sectorLoading = 0;
4374	},
4375	
4376	//set free tickets attribute for sectors
4377	setSectorFreeTickets: function(sectors) {
4378		for(var sector_id in sectors) {
4379			var sectorObj = $('a#testing-sector-' + sector_id, this.sectorsDiv);
4380			if ($(sectorObj).size() === 1) {
4381				$(sectorObj).attr('data-seats', sectors[sector_id]);
4382			}
4383		}
4384	},
4385	
4386	//symulate a click event on a sector object
4387	clickSector: function(sectorID) {
4388		if (this.sectorLoading === 0) {
4389			sectorID = parseInt(sectorID, 10);
4390			if (sectorID > 0) {
4391				this.sectorLoading = sectorID;
4392				$(this.seatsDiv).attr('data-loading', 'true');
4393				document.dispatchEvent(new CustomEvent("testSelection", { detail: {
4394					type: 'click-sector',
4395					data: {
4396						id: sectorID
4397					}
4398				}}));
4399			}
4400		}
4401	},
4402	
4403	//symulate a click event on a seat object
4404	clickSeat: function(seatID) {
4405		seatID = parseInt(seatID, 10);
4406		if (seatID > 0) {
4407			document.dispatchEvent(new CustomEvent("testSelection", { detail: {
4408				type: 'click-seat',
4409				data: {
4410					id: seatID
4411				}
4412			}}));
4413		}
4414	},
4415	
4416	//mark clicked object
4417	mark: function(domObj) {
4418		$(domObj).attr('style', 'color:YELLOW; background:BLUE;');
4419	}
4420};
4421
4422var AuditSocket = {
4423	socket: false,
4424	started: false,
4425	room: '',
4426	
4427	//my sendings - ignore on response
4428	mySeatActions: {},
4429	lastSeatID: 0,
4430	
4431	//queue sending
4432	queueList: [],
4433	queueIndex: 0,
4434	queueRun: false,
4435	
4436	init: function(host, port, room_prefix) {
4437		if (typeof auditview_token === 'undefined' || this.started) {
4438			//token required or is already started
4439			return false;
4440		}
4441		
4442		this.room = room_prefix + audit.eventId.toString();
4443		this.socket = new WebSocket('wss://' + host + ':' + port + '?token=' + auditview_token);
4444		
4445		this.socket.addEventListener('open', (event) => {
4446			AuditSocket.started = true;
4447			//console.log('Socket ready');
4448			setTimeout(() => {
4449				AuditSocket.setRoom();
4450			}, 50);
4451		});
4452	
4453		this.socket.addEventListener('close', (event) => {
4454			//console.log('Socket is closed', JSON.stringify(event));
4455			AuditSocket.started = false;
4456		});
4457		
4458		this.socket.addEventListener('message', (event) => {
4459			const message = event.data;
4460			//console.log("Received message: " + message);
4461			if (message === 'ok') {
4462				if (AuditSocket.queueRun) {
4463					AuditSocket.processQueue();
4464				}
4465			} else if (message !== '') {
4466				var operation = message.substring(0, 1);
4467				var parameters = message.substring(1);
4468				AuditSocket.processResult(operation, parameters);
4469			}
4470		});
4471		
4472		return true;
4473	},
4474	
4475	setRoom: function() {
4476		this.send('>' + this.room);
4477	},
4478	
4479	selectSeat: function(seatID) {
4480		this.lastSeatID = seatID;
4481		this.mySeatActions[seatID] = true;
4482		this.queue('+' + this.room + ':' + seatID);
4483	},
4484	
4485	unselectSeat: function(seatID) {
4486		this.lastSeatID = seatID;
4487		this.mySeatActions[seatID] = true;
4488		this.queue('-' + this.room + ':' + seatID);
4489	},
4490	
4491	updateSector: function(sectorID) {
4492		var seats = SeatingChart.getSectorSeats(sectorID);
4493		var lastSeatID = this.lastSeatID;
4494		if (seats.length < 15) {
4495			//special sectors are small
4496			for(var i=0; i<seats.length; i++) {
4497				if (seats[i].id == lastSeatID) {
4498					//not send again clicket seat
4499					continue;
4500				}
4501				if (seats[i].status === 0) {
4502					this.unselectSeat(seats[i].id);
4503				} else {
4504					this.selectSeat(seats[i].id);
4505				}
4506			}
4507		}
4508	},
4509	
4510	queue: function(message) {
4511		this.queueList.push(message);
4512		if (!this.queueRun) {
4513			this.processQueue();
4514		}
4515	},
4516	
4517	processQueue: function() {
4518		if (!this.queueRun && this.queueList.length > 0) {
4519			//start queue
4520			this.queueRun = true;
4521			this.queueIndex = 0;
4522		}
4523		
4524		if (this.queueRun) {
4525			if (typeof this.queueList[this.queueIndex] !== 'undefined') {
4526				//send next message
4527				var message = this.queueList[this.queueIndex];
4528				this.queueIndex++;
4529				this.send(message);
4530			} else {
4531				//queue finished - reset
4532				this.queueList = [];
4533				this.queueRun = false;
4534			}
4535		}
4536	},
4537	
4538	send: function(message) {
4539		if (!this.started) {
4540			//not started
4541			//console.error('Socket not started');
4542		} else if (this.socket.readyState === WebSocket.OPEN) {
4543			//socket is ready: send message
4544			//console.log('send message: ' + message);
4545			this.socket.send(message);
4546		} else {
4547			//socket bad status
4548			//console.error('Socket not ready');
4549		}
4550	},
4551	
4552	processResult: function(operation, parameters) {
4553		if (operation === '+' || operation === '-') {
4554			//+ a seat has been selected - mark as unavailable
4555			//- a seat has been unselected - mark as available
4556			if (typeof this.mySeatActions[parameters] !== 'undefined' && this.mySeatActions[parameters]) {
4557				//ignore operation - is my action
4558				this.mySeatActions[parameters] = false;
4559			} else {
4560				var seat_new_status = operation === '+' ? 2 : 0; //2-unavailable 0-available
4561				var seat_id = parseInt(parameters, 10);
4562				var price = Seat.getTicketPrice(seat_id);
4563				var groupd_id = operation === '+'
4564					? 0 //remove group on select or add group on release
4565					: (price in TicketGroup.list ? TicketGroup.list[price].index : TicketGroup.getGroupIndexByPrice(price));
4566				if (groupd_id === false) {
4567					//calculate group if required
4568					groupd_id = price > 0 ? (price * 1000 + 1) : 0;
4569				}
4570				Seat.changeStatusWithRefresh(seat_id, seat_new_status, groupd_id);
4571				if (operation === '+') {
4572					TicketGroup.decreaseTicket(price);
4573				} else {
4574					TicketGroup.increaseTicket(price);
4575				}
4576				document.dispatchEvent(new CustomEvent("viewerRedraw"));
4577			}
4578		}
4579	}
4580};

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.