PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.9
VikBooking Hotel Booking Engine & PMS v1.6.9
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / views / overv / tmpl / default.php

default.php in VikBooking Hotel Booking Engine & PMS 1.6.9, at admin/views/overv/tmpl/default.php

3,373 lines 135.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 $rows = $this->rows;
14 $arrbusy = $this->arrbusy;
15 $wmonthsel = $this->wmonthsel;
16 $tsstart = $this->tsstart;
17 $lim0 = $this->lim0;
18 $navbut = $this->navbut;
19
20 $vbo_auth_pricing = JFactory::getUser()->authorise('core.vbo.pricing', 'com_vikbooking');
21 $vbo_app = VikBooking::getVboApplication();
22 JHtml::fetch('script', VBO_SITE_URI.'resources/jquery-ui.min.js');
23 JHtml::fetch('script', VBO_ADMIN_URI.'resources/js_upload/jquery.stickytableheaders.min.js');
24
25 // JS lang defs
26 JText::script('VBSAVE');
27 JText::script('VBPVIEWORDERSTHREE');
28 JText::script('VBEDITORDERTHREE');
29 JText::script('VBDAYS');
30 JText::script('VBDAY');
31 JText::script('VBMAILADULTS');
32 JText::script('VBMAILADULT');
33 JText::script('VBMAILCHILDREN');
34 JText::script('VBMAILCHILD');
35 JText::script('VBO_MISSING_SUBUNIT');
36 JText::script('VBO_SHOW_CANCELLATIONS');
37 JText::script('VBO_DRAG_SUBUNITS_SAMEDATE');
38 JText::script('VBPVIEWORDERCHANNEL');
39 JText::script('VBANNULLA');
40 JText::script('VIKLOADING');
41 JText::script('VBOVWDNDERRNOTENCELLS');
42 JText::script('VBOVWDNDMOVINGBID');
43 JText::script('VBOVWDNDMOVINGROOM');
44 JText::script('VBOVWDNDMOVINGDATES');
45 JText::script('VBEDITORDERROOMSNUM');
46 JText::script('VBEDITORDERADULTS');
47 JText::script('VBPICKUPAT');
48 JText::script('VBRELEASEAT');
49 JText::script('VBEDITORDERCHILDREN');
50 JText::script('VBEDITORDERNINE');
51 JText::script('VBPEDITBUSYTOTPAID');
52 JText::script('VBOFEATASSIGNUNIT');
53 JText::script('VBO_SPLIT_STAY');
54 JText::script('VBO_CONF_SWAP_RNUMB');
55 JText::script('VBO_ERR_SUBUN_MOVE_SUBUN');
56 JText::script('VBO_BTYPE_OVERBOOKING');
57
58 $days_labels = [
59 JText::translate('VBSUN'),
60 JText::translate('VBMON'),
61 JText::translate('VBTUE'),
62 JText::translate('VBWED'),
63 JText::translate('VBTHU'),
64 JText::translate('VBFRI'),
65 JText::translate('VBSAT')
66 ];
67
68 $nowdf = VikBooking::getDateFormat();
69 if ($nowdf == "%d/%m/%Y") {
70 $df = 'd/m/Y';
71 } elseif ($nowdf == "%m/%d/%Y") {
72 $df = 'm/d/Y';
73 } else {
74 $df = 'Y/m/d';
75 }
76 $currencysymb = VikBooking::getCurrencySymb(true);
77
78 $pdebug = VikRequest::getInt('e4j_debug', '', 'request');
79 $session = JFactory::getSession();
80 $show_type = $session->get('vbUnitsShowType', '');
81 $mnum = $session->get('vbOvwMnum', '1');
82 $mnum = intval($mnum);
83 $pcategory_id = $session->get('vbOvwCatid', 0);
84 $cookie = JFactory::getApplication()->input->cookie;
85 $cookie_uleft = $cookie->get('vboAovwUleft', '', 'string');
86 $cookie_sticky_heads = $cookie->get('vboAovwStheads', 'off', 'string');
87 $colortags = VikBooking::loadBookingsColorTags();
88
89 // View mode - Classic or Tags
90 $pbmode = $session->get('vbTagsMode', 'classic');
91 $tags_view_supported = false;
92
93 // Room Units Distinctive Features
94 $rooms_features_map = [];
95 $rooms_features_bookings = [];
96 $rooms_bids_pools = [];
97 $room_bookings_pool = [];
98 $bids_checkins = [];
99 $index_loop = 0;
100 foreach ($rows as $kr => $room) {
101 if ($room['units'] <= 1) {
102 $tags_view_supported = true;
103 } else {
104 if (!empty($room['params']) && $room['units'] <= 150) {
105 //sub-room units only if room type has 150 units at most
106 $room_params = json_decode($room['params'], true);
107 if (is_array($room_params) && isset($room_params['features']) && is_array($room_params['features'])) {
108 $rooms_features_map[$room['id']] = [];
109 foreach ($room_params['features'] as $rind => $rfeatures) {
110 foreach ($rfeatures as $fname => $fval) {
111 if (strlen($fval)) {
112 // $rooms_features_map[$room['id']][$rind] = '#'.$rind.' - '.JText::translate($fname).': '.$fval;
113 $rooms_features_map[$room['id']][$rind] = "#{$rind} - {$fval}";
114 break;
115 }
116 }
117 }
118 if (!$rooms_features_map[$room['id']]) {
119 unset($rooms_features_map[$room['id']]);
120 } else {
121 foreach ($rooms_features_map[$room['id']] as $rind => $indexdata) {
122 $clone_room = $room;
123 $clone_room['unit_index'] = (int)$rind;
124 $clone_room['unit_index_str'] = $indexdata;
125 array_splice($rows, ($kr + 1 + $index_loop), 0, [$clone_room]);
126 $index_loop++;
127 }
128 }
129 }
130 }
131 }
132 }
133
134 if (!$tags_view_supported) {
135 $pbmode = 'classic';
136 }
137
138 // locked (stand-by) records
139 $arrlocked = [];
140 if (array_key_exists('tmplock', $arrbusy)) {
141 if (count($arrbusy['tmplock']) > 0) {
142 $arrlocked = $arrbusy['tmplock'];
143 }
144 unset($arrbusy['tmplock']);
145 }
146
147 ?>
148 <script type="text/Javascript">
149 function vboUnitsLeftOrBooked() {
150 var set_to = jQuery('#uleftorbooked').val();
151 jQuery('.vbo-overv-avcell[data-units-booked]').each(function() {
152 var counter_el = jQuery(this).find('a');
153 if (!counter_el.length) {
154 return;
155 }
156 counter_el.first().text(jQuery(this).attr('data-'+set_to));
157 });
158 var nd = new Date();
159 nd.setTime(nd.getTime() + (365*24*60*60*1000));
160 document.cookie = "vboAovwUleft="+set_to+"; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
161 }
162 if (jQuery.isFunction(jQuery.fn.tooltip)) {
163 jQuery(".hasTooltip").tooltip();
164 } else {
165 jQuery.fn.tooltip = function(){};
166 }
167 var vboFests = <?php echo json_encode($this->festivities); ?>;
168 var vboRdayNotes = <?php echo json_encode($this->rdaynotes); ?>;
169 </script>
170 <form action="index.php?option=com_vikbooking&amp;task=overv" method="post" name="vboverview" class="vbo-avov-form">
171 <div class="btn-toolbar vbo-avov-toolbar" id="filter-bar" style="width: 100%; display: inline-block;">
172 <div class="btn-group pull-left">
173 <?php echo $wmonthsel; ?>
174 </div>
175 <div class="btn-group pull-left">
176 <select name="mnum" onchange="document.vboverview.submit();">
177 <?php
178 for($i = 1; $i <= 18; $i++) {
179 ?>
180 <option value="<?php echo $i; ?>"<?php echo $i == $mnum ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBOVWNUMMONTHS').' '.$i; ?></option>
181 <?php
182 }
183 ?>
184 </select>
185 </div>
186 <?php
187 if (count($this->categories)) {
188 ?>
189 <div class="btn-group pull-left">
190 <select name="category_id" onchange="document.vboverview.submit();">
191 <option value="0">- <?php echo JText::translate('VBPVIEWROOMTWO'); ?> -</option>
192 <?php
193 foreach ($this->categories as $catid => $catname) {
194 ?>
195 <option value="<?php echo $catid; ?>"<?php echo $catid == $pcategory_id ? ' selected="selected"' : ''; ?>><?php echo $catname; ?></option>
196 <?php
197 }
198 ?>
199 </select>
200 </div>
201 <?php
202 }
203
204 $stickyheaders_cmd_on = '';
205 $stickyheaders_cmd_off = '';
206 if ((count($rows) * $mnum) > 10) {
207 if (VBOPlatformDetection::isJoomla()) {
208 /**
209 * @joomlaonly fixed offset selector is ".navbar"
210 */
211 $stickyheaders_cmd_on = "jQuery('table.vboverviewtable').stickyTableHeaders({cacheHeaderHeight: true, fixedOffset: (jQuery('.navbar').length ? jQuery('.navbar') : jQuery('#subhead-container'))});";
212 } else {
213 /**
214 * @wponly fixed offset selector is "#wpadminbar"
215 */
216 $stickyheaders_cmd_on = "jQuery('table.vboverviewtable').stickyTableHeaders({cacheHeaderHeight: true, fixedOffset: jQuery('#wpadminbar')});";
217 }
218 $stickyheaders_cmd_off = "jQuery('table.vboverviewtable').stickyTableHeaders('destroy'); jQuery('th.bluedays').attr('style', '');";
219 }
220 ?>
221 <script type="text/javascript">
222 function vboToggleStickyTableHeaders(val) {
223 var sticky_heads_cval = 'off';
224 if (val > 0) {
225 sticky_heads_cval = 'on';
226 jQuery('table.vboverviewtable').addClass('vbo-overv-sticky-table-head-on');
227 jQuery('table.vboverviewtable').removeClass('vbo-overv-sticky-table-head-off');
228 <?php echo $stickyheaders_cmd_on; ?>
229 // stop listening to the document scroll event to handle the fixed table heads
230 document.removeEventListener('scroll', vboOvervHandleVScrollTableHeads);
231 // scan all tables for listening to their horizontal scrolling event
232 let tables = document.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
233 tables.forEach((table) => {
234 // stop listening to the table horizontal scroll events
235 table.parentNode.removeEventListener('scroll', vboOvervHandleHScrollTableHead);
236 });
237 } else {
238 jQuery('table.vboverviewtable').addClass('vbo-overv-sticky-table-head-off');
239 jQuery('table.vboverviewtable').removeClass('vbo-overv-sticky-table-head-on');
240 <?php echo $stickyheaders_cmd_off; ?>
241 // listen to the document scroll event to handle the fixed table heads
242 document.addEventListener('scroll', vboOvervHandleVScrollTableHeads);
243 // scan all tables for listening to their horizontal scrolling event
244 let tables = document.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
245 tables.forEach((table) => {
246 table.parentNode.addEventListener('scroll', vboOvervHandleHScrollTableHead);
247 });
248 }
249 var nd = new Date();
250 nd.setTime(nd.getTime() + (365*24*60*60*1000));
251 document.cookie = "vboAovwStheads=" + sticky_heads_cval + "; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
252 }
253 </script>
254 <?php
255 if ((count($rows) * $mnum) > 10) {
256 ?>
257 <div class="btn-group pull-left">
258 <select name="stickytheads" onchange="vboToggleStickyTableHeaders(this.value);">
259 <optgroup label="<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_LAYOUT')); ?>">
260 <option value="1"<?php echo empty($cookie_sticky_heads) || $cookie_sticky_heads == 'on' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBO_TABLE'); ?></option>
261 <option value="0"<?php echo $cookie_sticky_heads == 'off' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBO_SCROLL'); ?></option>
262 </optgroup>
263 </select>
264 </div>
265 <?php
266 }
267
268 if ($tags_view_supported === true) {
269 ?>
270 <div class="btn-group pull-left">
271 <div class="vbo-overview-tagsorclassic">
272 <select name="bmode" onchange="document.vboverview.submit();">
273 <option value="classic"><?php echo JText::translate('VBOAVOVWBMODECLASSIC'); ?></option>
274 <option value="tags"<?php echo $pbmode == 'tags' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBOAVOVWBMODETAGS'); ?></option>
275 </select>
276 </div>
277 <?php
278 if ($pbmode == 'tags' && count($colortags) > 0) {
279 ?>
280 <div class="vbo-overview-tagslegend">
281 <span class="vbo-overview-tagslegend-lbl"><?php echo JText::translate('VBOAVOVWBMODETAGSLBL'); ?></span>
282 <?php
283 foreach ($colortags as $ctagk => $ctagv) {
284 ?>
285 <div class="vbo-overview-legend-tag">
286 <div class="vbo-colortag-circle hasTooltip" style="background-color: <?php echo $ctagv['color']; ?>;" title="<?php echo JText::translate($ctagv['name']); ?>"></div>
287 </div>
288 <?php
289 }
290 ?>
291 </div>
292 <?php
293 }
294 ?>
295 </div>
296 <?php
297 }
298 ?>
299 <div class="btn-group pull-right">
300 <select name="units_show_type" id="uleftorbooked" onchange="vboUnitsLeftOrBooked();"><option value="units-booked"<?php echo (!empty($cookie_uleft) && $cookie_uleft == 'units-booked' ? ' selected="selected"' : ''); ?>><?php echo JText::translate('VBOVERVIEWUBOOKEDFILT'); ?></option><option value="units-left"<?php echo $show_type == 'units-left' || (!empty($cookie_uleft) && $cookie_uleft == 'units-left') ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBOVERVIEWULEFTFILT'); ?></option></select>
301 </div>
302 <div class="btn-group pull-right vbo-avov-legend">
303 <span class="vbo-overview-legend-init"><?php echo JText::translate('VBOVERVIEWLEGEND'); ?></span>
304 <div class="vbo-overview-legend-red">
305 <span class="vbo-overview-legend-box">&nbsp;</span>
306 <span class="vbo-overview-legend-title"><?php echo JText::translate('VBOVERVIEWLEGRED'); ?></span>
307 </div>
308 <div class="vbo-overview-legend-yellow">
309 <span class="vbo-overview-legend-box">&nbsp;</span>
310 <span class="vbo-overview-legend-title"><?php echo JText::translate('VBOVERVIEWLEGYELLOW'); ?></span>
311 </div>
312 <div class="vbo-overview-legend-green">
313 <span class="vbo-overview-legend-box">&nbsp;</span>
314 <span class="vbo-overview-legend-title"><?php echo JText::translate('VBOVERVIEWLEGGREEN'); ?></span>
315 </div>
316 <div class="vbo-overview-legend-green vbo-overview-legend-dnd">
317 <span class="vbo-overview-legend-box"><i class="vboicn-enlarge" style="margin: 1px; display: block; text-align: center;"></i></span>
318 <span class="vbo-overview-legend-title"><?php echo JText::translate('VBOVERVIEWLEGDND'); ?></span>
319 </div>
320 </div>
321 </div>
322 </form>
323
324 <?php
325 $todayymd = date('Y-m-d');
326 $nowts = getdate($tsstart);
327 $curts = $nowts;
328 for ($mind = 1; $mind <= $mnum; $mind++) {
329 $monthname = VikBooking::sayMonth($curts['mon']);
330 ?>
331 <div class="vbo-overv-montable-wrap">
332 <div class="vbo-table-responsive">
333 <table class="vboverviewtable vbo-roverview-table vbo-table <?php echo $cookie_sticky_heads == 'off' ? 'vbo-overv-sticky-table-head-off' : 'vbo-overv-sticky-table-head-on'; ?>" data-table-index="<?php echo ($mind - 1); ?>">
334 <thead>
335 <tr class="vboverviewtablerowone">
336 <th class="bluedays skip-bluedays-click vbo-overview-month"><?php echo $monthname . " " . $curts['year']; ?></th>
337 <?php
338 $moncurts = $curts;
339 $mon = $moncurts['mon'];
340 while ($moncurts['mon'] == $mon) {
341 $curdayymd = date('Y-m-d', $moncurts[0]);
342 $read_day = $days_labels[$moncurts['wday']] . ' ' . $moncurts['mday'] . ' ' . $monthname . ' ' . $curts['year'];
343 ?>
344 <th class="bluedays<?php echo ($todayymd == $curdayymd ? ' vbo-overv-todaycell' : '') . (isset($this->festivities[$curdayymd]) ? ' vbo-overv-festcell' : ''); ?>" data-ymd="<?php echo $curdayymd; ?>" data-readymd="<?php echo $read_day; ?>">
345 <span class="vbo-overw-tablewday"><?php echo $days_labels[$moncurts['wday']]; ?></span>
346 <span class="vbo-overw-tablemday"><?php echo $moncurts['mday']; ?></span>
347 </th>
348 <?php
349 // parse the next day
350 $moncurts = getdate(mktime(0, 0, 0, $moncurts['mon'], ($moncurts['mday'] + 1), $moncurts['year']));
351 }
352 ?>
353 </tr>
354 </thead>
355 <tbody>
356 <?php
357 foreach ($rows as $room) {
358 $moncurts = $curts;
359 $mon = $moncurts['mon'];
360 $room_tags_view = $pbmode == 'tags' && $tags_view_supported === true && $room['units'] <= 1 ? true : false;
361 $is_subunit = array_key_exists('unit_index', $room);
362
363 ?>
364 <tr class="vboverviewtablerow<?php echo $is_subunit ? ' vboverviewtablerow-subunit' : ''; ?><?php echo ($is_subunit || $room['units'] == 1) && $cookie_sticky_heads == 'off' ? ' vbo-overv-row-snake' : ''; ?>"<?php echo $is_subunit ? ' data-subroomid="' . $room['id'] . '-' . $room['unit_index'] . '"' : ''; ?>>
365 <?php
366 if ($is_subunit) {
367 ?>
368 <td class="roomname subroomname" data-roomid="<?php echo '-' . $room['id']; ?>">
369 <span class="vbo-overview-subroomunits"><?php VikBookingIcons::e('bed'); ?></span>
370 <span class="vbo-overview-subroomname"><?php echo $room['unit_index_str']; ?></span>
371 </td>
372 <?php
373 } else {
374 ?>
375 <td class="roomname" data-roomid="<?php echo $room['id']; ?>">
376 <span class="vbo-overview-room-info">
377 <span class="vbo-overview-roomunits"><?php echo $room['units']; ?></span>
378 <span class="vbo-overview-roomname"><?php echo $room['name']; ?></span>
379 <?php
380 if (isset($rooms_features_map[$room['id']])) {
381 ?>
382 <span class="vbo-overview-subroom-toggle"><i class="<?php echo VikBookingIcons::i('chevron-down', 'hasTooltip'); ?>" style="margin: 0;" title="<?php echo JHtml::fetch('esc_attr', JText::translate('VBOVERVIEWTOGGLESUBROOM')); ?>"></i></span>
383 <?php
384 }
385 ?>
386 </span>
387 </td>
388 <?php
389 }
390
391 // loop through every day of the month
392 $room_bids_pool = [];
393 $room_bookings = [];
394 while ($moncurts['mon'] == $mon) {
395 $dclass = 'vbo-overv-avcell ' . (!$is_subunit ? "notbusy" : "subnotbusy");
396 $is_checkin = false;
397 $is_sharedcal = false;
398 $is_closure = false;
399 $lastbidcheckout = null;
400 $dalt = "";
401 $bid = "";
402 $bids_pool = [];
403 $totfound = 0;
404 $prev_day_key = date('Y-m-d', strtotime('-1 day', $moncurts[0]));
405 $cur_day_key = date('Y-m-d', $moncurts[0]);
406 if (!empty($arrbusy[$room['id']]) && !$is_subunit) {
407 foreach ($arrbusy[$room['id']] as $b) {
408 $tmpone = getdate($b['checkin']);
409 $ritts = mktime(0, 0, 0, $tmpone['mon'], $tmpone['mday'], $tmpone['year']);
410 $tmptwo = getdate($b['checkout']);
411 $conts = mktime(0, 0, 0, $tmptwo['mon'], $tmptwo['mday'], $tmptwo['year']);
412 if (!($moncurts[0] >= $ritts && $moncurts[0] < $conts)) {
413 // booking does not involve the current day
414 continue;
415 }
416 $dclass = "vbo-overv-avcell busy";
417 $bid = $b['idorder'];
418 $is_sharedcal = !empty($b['sharedcal']) ? true : $is_sharedcal;
419 $is_closure = !empty($b['closure']) ? true : $is_closure;
420 $bid_str = '-' . $bid . '-';
421 if (!in_array($bid_str, $bids_pool)) {
422 $bids_pool[] = $bid_str;
423 }
424 if (isset($rooms_features_map[$room['id']])) {
425 // multi-unit room with distinctive features defined
426 if (!isset($room_bids_pool[$cur_day_key])) {
427 $room_bids_pool[$cur_day_key] = [];
428 $room_bookings[$cur_day_key] = [];
429 }
430 $room_bids_pool[$cur_day_key][] = (int)$bid;
431 $room_bookings[$cur_day_key][] = $b;
432 } elseif ($room['units'] == 1) {
433 // single-unit room
434 if (!isset($room_bookings[$cur_day_key])) {
435 $room_bookings[$cur_day_key] = [];
436 }
437 $room_bookings[$cur_day_key][] = $b;
438 }
439 if ($moncurts[0] == $ritts) {
440 $dalt = JText::translate('VBPICKUPAT')." ".date('H:i', $b['checkin']);
441 $is_checkin = true;
442 $lastbidcheckout = $b['checkout'];
443 $bids_checkins[$bid] = $cur_day_key;
444 } elseif ($moncurts[0] == $conts) {
445 $dalt = JText::translate('VBRELEASEAT')." ".date('H:i', $b['checkout']);
446 }
447 $totfound++;
448 }
449 }
450
451 // locked (stand-by) records
452 if ($room_tags_view === true && isset($arrlocked[$room['id']]) && $arrlocked[$room['id']] && !$is_subunit) {
453 foreach ($arrlocked[$room['id']] as $l) {
454 $tmpone = getdate($l['checkin']);
455 $ritts = mktime(0, 0, 0, $tmpone['mon'], $tmpone['mday'], $tmpone['year']);
456 $tmptwo = getdate($l['checkout']);
457 $conts = mktime(0, 0, 0, $tmptwo['mon'], $tmptwo['mday'], $tmptwo['year']);
458 if ($moncurts[0] >= $ritts && $moncurts[0] < $conts) {
459 $dclass = strpos($dclass, "notbusy") !== false ? "busytmplock" : "busy busytmplock";
460 $bid = $l['idorder'];
461 if (!in_array($bid, $bids_pool)) {
462 if (count($bids_pool) > 0) {
463 array_unshift($bids_pool, '-'.$bid.'-');
464 } else {
465 $bids_pool[] = '-'.$bid.'-';
466 }
467 }
468 if ($moncurts[0] == $ritts) {
469 $dalt = JText::translate('VBPICKUPAT')." ".date('H:i', $l['checkin']);
470 } elseif ($moncurts[0] == $conts) {
471 $dalt = JText::translate('VBRELEASEAT')." ".date('H:i', $l['checkout']);
472 }
473 $totfound++;
474 }
475 }
476 }
477
478 // handle single-unit room reservations
479 if ($room['units'] == 1 && !isset($rooms_features_map[$room['id']]) && isset($room_bookings[$cur_day_key]) && $room_bookings[$cur_day_key]) {
480 // set the first (and only) booking
481 $day_booking = [];
482 foreach ($room_bookings[$cur_day_key] as $day_res) {
483 if (!$day_res['closure'] && !$day_res['sharedcal']) {
484 $day_booking = $day_res;
485 break;
486 }
487 }
488 if ($day_booking) {
489 if (!isset($room_bookings_pool[$room['id']])) {
490 $room_bookings_pool[$room['id']] = [];
491 }
492 if (!isset($room_bookings_pool[$room['id']][$cur_day_key])) {
493 $room_bookings_pool[$room['id']][$cur_day_key] = [];
494 }
495 $room_bookings_pool[$room['id']][$cur_day_key][0] = $day_booking;
496 }
497 }
498
499 $useday = $moncurts['mday'] < 10 ? "0{$moncurts['mday']}" : $moncurts['mday'];
500 $dclass .= $totfound < $room['units'] && $totfound > 0 ? ' vbo-partially' : '';
501 $dclass .= $is_sharedcal ? ' busy-sharedcalendar' : '';
502 $dclass .= $is_closure ? ' busy-closure' : '';
503 $dstyle = '';
504 $astyle = '';
505 if ($room_tags_view === true && $totfound > 0) {
506 $last_bid = intval(str_replace('-', '', $bids_pool[(count($bids_pool) - 1)]));
507 $binfo = VikBooking::getBookingInfoFromID($last_bid);
508 if (count($binfo) > 0) {
509 $bcolortag = VikBooking::applyBookingColorTag($binfo);
510 if (count($bcolortag) > 0) {
511 $bcolortag['name'] = JText::translate($bcolortag['name']);
512 $dstyle = " style=\"background-color: ".$bcolortag['color']."; color: ".(array_key_exists('fontcolor', $bcolortag) ? $bcolortag['fontcolor'] : '#ffffff').";\" data-lastbid=\"".$last_bid."\"";
513 $astyle = " style=\"color: ".(array_key_exists('fontcolor', $bcolortag) ? $bcolortag['fontcolor'] : '#ffffff').";\"";
514 $dclass .= ' vbo-hascolortag';
515 }
516 }
517 }
518 if ($is_subunit && isset($rooms_features_bookings[$room['id']]) && isset($rooms_bids_pools[$room['id']][$cur_day_key]) && isset($rooms_features_bookings[$room['id']][$room['unit_index']])) {
519 foreach ($rooms_bids_pools[$room['id']][$cur_day_key] as $bid) {
520 $bid = intval(str_replace('-', '', $bid));
521 if (in_array($bid, $rooms_features_bookings[$room['id']][$room['unit_index']])) {
522 $room['units'] = 1;
523 $totfound = 1;
524 $dclass = "vbo-overv-avcell subroom-busy";
525 $is_checkin = isset($bids_checkins[$bid]) && $bids_checkins[$bid] == $cur_day_key ? true : $is_checkin;
526 break;
527 }
528 }
529 }
530 $write_units = $show_type == 'units-left' || (!empty($cookie_uleft) && $cookie_uleft == 'units-left') ? ($room['units'] - $totfound) : $totfound;
531 // check today's date
532 $curdayymd = date('Y-m-d', $moncurts[0]);
533 if ($todayymd == $curdayymd) {
534 $dclass .= ' vbo-overv-todaycell';
535 }
536 if (isset($this->festivities[$curdayymd])) {
537 $dclass .= ' vbo-overv-festcell';
538 }
539
540 /**
541 * Critical dates defined at room-day level.
542 *
543 * @since 1.13.5 (J) - 1.3.5 (WP)
544 */
545 $rdaynote_keyid = $cur_day_key . '_' . $room['id'] . '_' . (isset($room['unit_index']) ? $room['unit_index'] : '0');
546 if (isset($this->rdaynotes[$rdaynote_keyid])) {
547 // note exists for this combination of date, room ID and subunit
548 $dclass .= ' vbo-roomdaynote-full';
549 $rdaynote_icn = 'sticky-note';
550 } else {
551 // no notes for this cell
552 $dclass .= ' vbo-roomdaynote-empty';
553 $rdaynote_icn = 'far fa-sticky-note';
554 }
555 $critical_note = '<span class="vbo-roomdaynote-trigger" data-roomday="' . $rdaynote_keyid . '"><i class="' . VikBookingIcons::i($rdaynote_icn, 'vbo-roomdaynote-display') . '"></i></span>';
556
557 /**
558 * Closures overlapping one real reservation for a single-unit room should still
559 * allow the display of the booking snake if it's just one real reservation.
560 */
561 $closure_on_real_res = false;
562 if ($totfound === 2 && !$is_subunit && $room['units'] == 1 && $cookie_sticky_heads == 'off' && strpos($dclass, "busy-closure") !== false) {
563 if (isset($room_bookings_pool[$room['id']]) && isset($room_bookings_pool[$room['id']][$cur_day_key]) && $room_bookings_pool[$room['id']][$cur_day_key]) {
564 if (isset($room_bookings_pool[$room['id']][$cur_day_key][0]) && !$room_bookings_pool[$room['id']][$cur_day_key][0]['closure']) {
565 // found a real booking on a date with at least one closure
566 $closure_on_real_res = true;
567 }
568 }
569 }
570
571 // prepare cell content
572 if ($totfound === 1 || $closure_on_real_res) {
573 $day_booking_snake = '';
574 $day_booking_data = [];
575 if (isset($room_bookings_pool[$room['id']]) && isset($room_bookings_pool[$room['id']][$cur_day_key]) && $room_bookings_pool[$room['id']][$cur_day_key]) {
576 if ($is_subunit && isset($room_bookings_pool[$room['id']][$cur_day_key][$room['unit_index']])) {
577 $day_booking_data = $room_bookings_pool[$room['id']][$cur_day_key][$room['unit_index']];
578 } elseif (!$is_subunit && isset($room_bookings_pool[$room['id']][$cur_day_key][0])) {
579 $day_booking_data = $room_bookings_pool[$room['id']][$cur_day_key][0];
580 }
581 }
582 if (isset($room_bookings_pool[$room['id']]) && isset($room_bookings_pool[$room['id']][$prev_day_key]) && $room_bookings_pool[$room['id']][$prev_day_key]) {
583 if ($is_subunit && isset($room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']])) {
584 if (date('Y-m-d', $room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']]['checkout']) == $cur_day_key) {
585 // prepend checkout snake
586 $day_booking_snake .= '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
587 }
588 } elseif (!$is_subunit && $room['units'] == 1 && isset($room_bookings_pool[$room['id']][$prev_day_key][0])) {
589 if (date('Y-m-d', $room_bookings_pool[$room['id']][$prev_day_key][0]['checkout']) == $cur_day_key) {
590 // prepend checkout snake
591 $day_booking_snake .= '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
592 }
593 }
594 }
595 if ($room['units'] == 1 && $day_booking_data && !$day_booking_data['closure']) {
596 // build tableaux-style snake container for guest
597 $customer_descr = '';
598 if ($is_checkin) {
599 // customer details
600 if (!empty($day_booking_data['first_name']) || !empty($day_booking_data['last_name'])) {
601 // check if we need to display a profile picture or a channel logo
602 $booking_avatar_src = null;
603 $booking_avatar_alt = null;
604 if (!empty($day_booking_data['pic'])) {
605 // customer profile picture
606 $booking_avatar_src = strpos($day_booking_data['pic'], 'http') === 0 ? $day_booking_data['pic'] : VBO_SITE_URI . 'resources/uploads/' . $day_booking_data['pic'];
607 $booking_avatar_alt = basename($booking_avatar_src);
608 } elseif (!empty($day_booking_data['idorderota']) && !empty($day_booking_data['channel'])) {
609 // channel logo
610 $logo_helper = VikBooking::getVcmChannelsLogo($day_booking_data['channel'], $get_istance = true);
611 if ($logo_helper !== false) {
612 $booking_avatar_src = $logo_helper->getSmallLogoURL();
613 $booking_avatar_alt = $logo_helper->provenience;
614 }
615 }
616
617 if (!empty($booking_avatar_src)) {
618 // make sure the alt attribute is not too long in case of broken images
619 $booking_avatar_alt = !empty($booking_avatar_alt) && strlen($booking_avatar_alt) > 15 ? '...' . substr($booking_avatar_alt, -12) : $booking_avatar_alt;
620 // append booking avatar image
621 $customer_descr .= '<span class="vbo-tableaux-booking-avatar"><img src="' . $booking_avatar_src . '" class="vbo-tableaux-booking-avatar-img" ' . (!empty($booking_avatar_alt) ? 'alt="' . htmlspecialchars($booking_avatar_alt) . '" ' : '') . '/></span>';
622 }
623
624 // customer name
625 $customer_fullname = trim($day_booking_data['first_name'] . ' ' . $day_booking_data['last_name']);
626 if (strlen($customer_fullname) > 26) {
627 if (function_exists('mb_substr')) {
628 $customer_fullname = trim(mb_substr($customer_fullname, 0, 26, 'UTF-8')) . '..';
629 } else {
630 $customer_fullname = trim(substr($customer_fullname, 0, 26)) . '..';
631 }
632 }
633 $customer_descr .= '<span class="vbo-tableaux-guest-name">' . $customer_fullname . '</span>';
634 } else {
635 // parse the customer data string
636 $custdata_parts = explode("\n", $day_booking_data['custdata']);
637 $enoughinfo = false;
638 if (count($custdata_parts) > 2 && strpos($custdata_parts[0], ':') !== false && strpos($custdata_parts[1], ':') !== false) {
639 // get the first two fields
640 $custvalues = array();
641 foreach ($custdata_parts as $custdet) {
642 if (strlen($custdet) < 1) {
643 continue;
644 }
645 $custdet_parts = explode(':', $custdet);
646 if (count($custdet_parts) >= 2) {
647 unset($custdet_parts[0]);
648 array_push($custvalues, trim(implode(':', $custdet_parts)));
649 }
650 if (count($custvalues) > 1) {
651 break;
652 }
653 }
654 if (count($custvalues) > 1) {
655 $enoughinfo = true;
656 $customer_nominative = trim(implode(' ', $custvalues));
657 if (strlen($customer_nominative) > 26) {
658 if (function_exists('mb_substr')) {
659 $customer_nominative = trim(mb_substr($customer_nominative, 0, 26, 'UTF-8')) . '..';
660 } else {
661 $customer_nominative = trim(substr($customer_nominative, 0, 26)) . '..';
662 }
663 }
664 if (!empty($day_booking_data['idorderota']) && !empty($day_booking_data['channel'])) {
665 // add support for the channel logo for the imported OTA reservations with no customer record
666 $logo_helper = VikBooking::getVcmChannelsLogo($day_booking_data['channel'], $get_istance = true);
667 if ($logo_helper !== false) {
668 $booking_avatar_src = $logo_helper->getSmallLogoURL();
669 $booking_avatar_alt = $logo_helper->provenience;
670 // make sure the alt attribute is not too long in case of broken images
671 $booking_avatar_alt = !empty($booking_avatar_alt) && strlen($booking_avatar_alt) > 15 ? '...' . substr($booking_avatar_alt, -12) : $booking_avatar_alt;
672 // append booking avatar image
673 $customer_descr .= '<span class="vbo-tableaux-booking-avatar"><img src="' . $booking_avatar_src . '" class="vbo-tableaux-booking-avatar-img" ' . (!empty($booking_avatar_alt) ? 'alt="' . htmlspecialchars($booking_avatar_alt) . '" ' : '') . '/></span>';
674 }
675 }
676 // set customer nominative built
677 $customer_descr .= '<span class="vbo-tableaux-guest-name">' . $customer_nominative . '</span>';;
678 }
679 }
680 if (!$enoughinfo) {
681 $customer_descr .= '<span class="vbo-tableaux-guest-name">#' . $day_booking_data['idorder'] . '</span>';;
682 }
683 }
684 }
685 // set value
686 $day_booking_snake .= '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit ' . ($is_checkin ? 'vbo-tableaux-booking-checkin' : 'vbo-tableaux-booking-stay') . '"' . ($is_checkin ? ' data-nights="' . $day_booking_data['days'] . '" draggable="true"' : '') . '><span>' . ($is_checkin ? $customer_descr : '&nbsp;') . '</span></div>';
687 }
688
689 if ($cookie_sticky_heads != 'off') {
690 // not supported with the sticky table headers layout
691 $day_booking_snake = '';
692 }
693
694 $write_units = strpos($dclass, "subroom-busy") !== false ? '' : $write_units;
695 $stopdrag = ($mind == $mnum && !is_null($lastbidcheckout) && (int)date('n', $lastbidcheckout) != (int)$mon);
696 $dclass .= $is_checkin === true ? ' vbo-checkinday' : '';
697
698 // build proper link
699 if ($totfound > 1) {
700 // there must be a closure overlapping a real booking
701 $cell_link = 'index.php?option=com_vikbooking&task=choosebusy&idroom=' . $room['id'] . '&ts=' . $moncurts[0] . '&goto=overv';
702 } else {
703 // regular situation with one booking only
704 $cell_link = 'index.php?option=com_vikbooking&task=editbusy&cid[]=' . $bid . '&goto=overv';
705 }
706 ?>
707 <td align="center" class="<?php echo $dclass; ?>"<?php echo $dstyle; ?> data-day="<?php echo $cur_day_key; ?>" data-units-booked="<?php echo $totfound; ?>" data-units-left="<?php echo ($room['units'] - $totfound); ?>" data-bids="<?php echo strpos($dclass, "subroom-busy") !== false ? "-{$bid}-" : implode(',', $bids_pool); ?>">
708 <?php
709 if (!$day_booking_snake) {
710 if ($is_checkin === true && !$stopdrag) {
711 ?>
712 <span class="vbo-draggable-sp" draggable="true">
713 <a href="<?php echo $cell_link; ?>" class="<?php echo strpos($dclass, "subroom-busy") === false ? 'vbo-overview-redday' : 'vbo-overview-subredday'; ?>"<?php echo $astyle . (!empty($dalt) ? ' title="' . JHtml::fetch('esc_attr', $dalt) . '"' : ''); ?>><?php echo $write_units; ?></a>
714 </span>
715 <?php
716 } else {
717 ?>
718 <a href="<?php echo $cell_link; ?>" class="<?php echo strpos($dclass, "subroom-busy") === false ? 'vbo-overview-redday' : 'vbo-overview-subredday'; ?>"<?php echo $astyle . (!empty($dalt) ? ' title="' . JHtml::fetch('esc_attr', $dalt) . '"' : ''); ?>><?php echo $write_units; ?></a>
719 <?php
720 }
721 }
722 echo $day_booking_snake . $critical_note;
723 ?>
724 </td>
725 <?php
726 } elseif ($totfound > 1) {
727 ?>
728 <td align="center" class="<?php echo $dclass; ?>"<?php echo $dstyle; ?> data-day="<?php echo $cur_day_key; ?>" data-units-booked="<?php echo $totfound; ?>" data-units-left="<?php echo ($room['units'] - $totfound); ?>" data-bids="<?php echo implode(',', $bids_pool); ?>">
729 <a href="index.php?option=com_vikbooking&task=choosebusy&idroom=<?php echo $room['id']; ?>&ts=<?php echo $moncurts[0]; ?>&goto=overv" class="vbo-overview-redday"<?php echo $astyle; ?>><?php echo $write_units; ?></a>
730 <?php echo $critical_note; ?>
731 </td>
732 <?php
733 } else {
734 // no booked records
735 ?>
736 <td align="center" class="<?php echo $dclass; ?>" data-day="<?php echo $cur_day_key; ?>" data-bids="">
737 <?php
738 if ($cookie_sticky_heads == 'off' && isset($room_bookings_pool[$room['id']]) && isset($room_bookings_pool[$room['id']][$prev_day_key]) && $room_bookings_pool[$room['id']][$prev_day_key]) {
739 if ($is_subunit && isset($room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']])) {
740 if (date('Y-m-d', $room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']]['checkout']) == $cur_day_key) {
741 // prepend checkout snake
742 echo '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
743 }
744 } elseif (!$is_subunit && $room['units'] == 1 && isset($room_bookings_pool[$room['id']][$prev_day_key][0])) {
745 if (date('Y-m-d', $room_bookings_pool[$room['id']][$prev_day_key][0]['checkout']) == $cur_day_key) {
746 // prepend checkout snake
747 echo '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
748 }
749 }
750 }
751 echo $critical_note;
752 ?>
753 </td>
754 <?php
755 }
756
757 // iterate to next day
758 $moncurts = getdate(mktime(0, 0, 0, $moncurts['mon'], ($moncurts['mday'] + 1), $moncurts['year']));
759 }
760
761 // room row parsed, check if we have to parse a sub-unit next
762 if (!$is_subunit && $room_bids_pool && isset($rooms_features_map[$room['id']])) {
763 // load bookings for distinctive features when parsing the parent $room array
764 $room_indexes_bids = VikBooking::loadRoomIndexesBookings($room['id'], $room_bids_pool);
765 if ($room_indexes_bids) {
766 $rooms_features_bookings[$room['id']] = $room_indexes_bids;
767 $rooms_bids_pools[$room['id']] = $room_bids_pool;
768 // map sub-unit room bookings
769 foreach ($room_indexes_bids as $rindex => $rindex_bids) {
770 if (!is_array($rindex_bids) || !$rindex_bids) {
771 continue;
772 }
773 foreach ($rindex_bids as $seek_bid) {
774 foreach ($room_bookings as $day_key => $day_ress) {
775 foreach ($day_ress as $day_res) {
776 if ($day_res['idorder'] != $seek_bid) {
777 continue;
778 }
779 if (!isset($room_bookings_pool[$room['id']])) {
780 $room_bookings_pool[$room['id']] = [];
781 }
782 if (!isset($room_bookings_pool[$room['id']][$day_key])) {
783 $room_bookings_pool[$room['id']][$day_key] = [];
784 }
785 $room_bookings_pool[$room['id']][$day_key][$rindex] = $day_res;
786 }
787 }
788 }
789 }
790 }
791 }
792
793 // close row
794 ?>
795 </tr>
796 <?php
797 }
798 ?>
799 </tbody>
800 </table>
801 </div>
802 </div>
803 <?php
804 // iterate to next month
805 $curts = getdate(mktime(0, 0, 0, ($nowts['mon'] + $mind), $nowts['mday'], $nowts['year']));
806 }
807
808 //Prepare modal
809 ?>
810 <script type="text/javascript">
811 var hasNewBooking = false;
812 var last_room_click = '',
813 last_date_click = '',
814 next_date_click = '';
815 function vboJModalHideCallback() {
816 if (hasNewBooking === true) {
817 location.reload();
818 }
819 }
820 function vboCallOpenJModal(identif, baseurl) {
821 if (last_room_click && last_room_click.length) {
822 baseurl += '&cid[]='+last_room_click;
823 last_room_click = '';
824 }
825 if (last_date_click && last_date_click.length) {
826 baseurl += '&checkin='+last_date_click;
827 last_date_click = '';
828 }
829 if (next_date_click && next_date_click.length) {
830 baseurl += '&checkout='+next_date_click;
831 next_date_click = '';
832 }
833 vboOpenJModal(identif, baseurl);
834 }
835 </script>
836 <?php
837 echo $vbo_app->getJmodalScript('', 'vboJModalHideCallback();', '');
838 echo $vbo_app->getJmodalHtml('vbo-new-res', JText::translate('VBOSHOWQUICKRES'));
839 //end Prepare modal
840 ?>
841 <div class="vbo-ovrv-flt-butn" onclick="vboCallOpenJModal('vbo-new-res', 'index.php?option=com_vikbooking&task=calendar&overv=1&tmpl=component');"><span><i class="vboicn-user-plus"></i> <?php echo JText::translate('VBOSHOWQUICKRES'); ?></span></div>
842 <div class="vbo-info-overlay-block">
843 <div class="vbo-info-overlay-loading-dnd">
844 <span class="vbo-loading-dnd-head"></span>
845 <span class="vbo-loading-dnd-body"></span>
846 <span class="vbo-loading-dnd-footer"><?php echo JText::translate('VIKLOADING'); ?></span>
847 <span id="vbo-dnd-response" class="vbo-loading-dnd-response"></span>
848 <canvas id="vbo-dnd-canvas-success" height="250"></canvas>
849 </div>
850 </div>
851
852 <form action="index.php?option=com_vikbooking" method="post" name="adminForm" id="adminForm">
853 <input type="hidden" name="option" value="com_vikbooking" />
854 <input type="hidden" name="task" value="overv" />
855 <input type="hidden" name="month" value="<?php echo $tsstart; ?>" />
856 <input type="hidden" name="mnum" value="<?php echo $mnum; ?>" />
857 <input type="hidden" name="category_id" value="<?php echo $pcategory_id; ?>" />
858 <?php echo '<br/>'.$navbut; ?>
859 </form>
860
861 <a class="vbo-basenavuri-details" href="index.php?option=com_vikbooking&task=editorder&goto=overv&cid[]=%d" style="display: none;"></a>
862 <a class="vbo-basenavuri-edit" href="index.php?option=com_vikbooking&task=editbusy&goto=overv&cid[]=%d" style="display: none;"></a>
863
864 <script type="text/Javascript">
865 var hovtimer;
866 var hovtip = false;
867 var vbodialogorph_on = false;
868 var isdragging = false;
869 var vboMessages = {
870 currencySymb: "<?php echo $currencysymb; ?>"
871 };
872 var debug_mode = '<?php echo $pdebug; ?>';
873 var bctags_count = <?php echo count($colortags); ?>;
874 var bctags_pool = <?php echo json_encode($colortags); ?>;
875 <?php
876 if (count($colortags) > 0) {
877 $bctags_tip = '<div class=\"vbo-overview-tip-bctag-subtip-inner\">';
878 foreach ($colortags as $ctagk => $ctagv) {
879 $bctags_tip .= '<div class=\"vbo-overview-tip-bctag-subtip-circle hasTooltip\" data-ctagkey=\"'.$ctagk.'\" data-ctagcolor=\"'.$ctagv['color'].'\" title=\"'.addslashes(JText::translate($ctagv['name'])).'\"><div class=\"vbo-overview-tip-bctag-subtip-circlecont\" style=\"background-color: '.$ctagv['color'].';\"></div></div>';
880 }
881 $bctags_tip .= '</div>';
882 ?>
883 var bctags_tip = "<?php echo $bctags_tip; ?>";
884 <?php
885 }
886 ?>
887 </script>
888
889 <script type="text/Javascript">
890 /**
891 * Render the units view mode
892 */
893 vboUnitsLeftOrBooked();
894
895 /**
896 * Orphans dialog
897 */
898 function hideVboDialogOverv(action) {
899 if (vbodialogorph_on === true) {
900 jQuery(".vbo-orphans-overlay-block").fadeOut(400, function () {
901 jQuery(".vbo-info-overlay-content").show();
902 });
903 vbodialogorph_on = false;
904 }
905 // check action
906 if (action < 0) {
907 // stop reminding, set cookie
908 var nd = new Date();
909 nd.setTime(nd.getTime() + (365*24*60*60*1000));
910 document.cookie = "vboHideOrphans=1; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
911 }
912 }
913
914 /* DnD global vars */
915 var cellspool = [];
916 var newcellspool = [];
917
918 /* DnD Count the number of consecutive cells for this booking */
919 function countBookingCells(cellobj, bidstart, roomid) {
920 var totnights = 1;
921 var loop = true;
922 var cellelem = cellobj;
923 cellspool.push(cellelem);
924 while (loop === true) {
925 var next = cellelem.next('td');
926 if (next === undefined || !next.length) {
927 //attempt to go to the month after
928 var partable = cellelem.closest('tr').closest('table').nextAll('table.vboverviewtable').first();
929 var nextmonth = false;
930 if (partable !== undefined && partable.length) {
931 partable.find('tr.vboverviewtablerow').each(function() {
932 var roomexists = jQuery(this).find('td').first();
933 if (roomexists !== undefined && roomexists.length) {
934 if (roomexists.attr('data-roomid') == roomid) {
935 nextmonth = true;
936 next = roomexists.next('td');
937 return true;
938 }
939 }
940 });
941 }
942 if (nextmonth === false) {
943 //nothing was found in the month after
944 loop = false;
945 break;
946 }
947 }
948 cellelem = next;
949 var nextbids = cellelem.attr('data-bids');
950 if (nextbids.length && nextbids.indexOf(bidstart) >= 0) {
951 cellspool.push(cellelem);
952 totnights++;
953 } else {
954 loop = false;
955 break;
956 }
957 }
958 return totnights;
959 }
960
961 /* DnD Count the number of consecutive free date-cells for moving the booking onto the landing cell selected for drop */
962 function countCellFreeNights(landobj, roomid, totnights, moving_bids) {
963 var freenights = 1;
964 var loop = true;
965 var cellelem = landobj;
966
967 // populate cells pool
968 newcellspool.push(cellelem);
969
970 while (loop === true) {
971 var next = cellelem.next('td');
972 if (next === undefined || !next.length) {
973 // attempt to go to the month after
974 var partable = cellelem.closest('tr').closest('table').nextAll('table.vboverviewtable').first();
975 var nextmonth = false;
976 if (partable !== undefined && partable.length) {
977 partable.find('tr.vboverviewtablerow').each(function() {
978 var roomexists = jQuery(this).find('td').first();
979 if (roomexists !== undefined && roomexists.length) {
980 if (roomexists.attr('data-roomid') == roomid) {
981 nextmonth = true;
982 next = roomexists.next('td');
983 return true;
984 }
985 }
986 });
987 }
988 if (nextmonth === false) {
989 // nothing was found in the month after
990 loop = false;
991 break;
992 }
993 }
994 cellelem = next;
995 var cell_bids = cellelem.attr('data-bids');
996 if (!cellelem.hasClass('busy') || (cellelem.hasClass('busy') && cellelem.hasClass('vbo-partially'))) {
997 // bookings of 1 night can stop here because there is availability for one day
998 if (parseInt(totnights) === 1) {
999 loop = false;
1000 break;
1001 }
1002 // push free cell
1003 newcellspool.push(cellelem);
1004 freenights++;
1005 if (freenights >= totnights) {
1006 loop = false;
1007 break;
1008 }
1009 } else {
1010 // cell is occupied, check if it's occupied by the booking we are moving or not
1011 if (!cell_bids || cell_bids != moving_bids) {
1012 loop = false;
1013 break;
1014 }
1015 // cell is occupied by the same booking we are moving, so this cell counts as free
1016 newcellspool.push(cellelem);
1017 freenights++;
1018 if (freenights >= totnights) {
1019 loop = false;
1020 break;
1021 }
1022 }
1023 }
1024
1025 return freenights;
1026 }
1027
1028 /* DnD function to perform the ajax request of the booking modification */
1029 function doAlterBooking(bid, roomid, landrid) {
1030 var nowdatefrom = jQuery(newcellspool[0]).attr('data-day');
1031 var nowdateto = jQuery(newcellspool[(newcellspool.length -1)]).attr('data-day');
1032
1033 // perform the request
1034 VBOCore.doAjax(
1035 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=alterbooking'); ?>",
1036 {
1037 tmpl: "component",
1038 idorder: bid,
1039 oldidroom: roomid,
1040 idroom: landrid,
1041 fromdate: nowdatefrom,
1042 todate: nowdateto,
1043 e4j_debug: debug_mode
1044 },
1045 (res) => {
1046 if (res.indexOf('e4j.error') >= 0 ) {
1047 // console.log('doAlterBooking-- Booking ID: '+bid+' - Old Room ID: '+roomid+' - New Room ID: '+landrid+' - New Date From: '+nowdatefrom+' - New Date To: '+nowdateto);
1048 // console.log(res);
1049 alert(res.replace("e4j.error.", ""));
1050 //restore the old cells
1051 jQuery('td.vbo-dragging-cells-tmp').removeClass('vbo-dragging-cells-tmp');
1052 jQuery('td.vbo-dragged-cells-tmp').removeClass('vbo-dragged-cells-tmp');
1053 //
1054 jQuery(".vbo-info-overlay-block").hide();
1055 } else {
1056 //move to the new cells and if there are already bookings on those dates, reload the page without moving the blocks
1057 var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
1058 var mustReload = obj_res.esit < 1 ? true : false;
1059 //always force reload if booking was made for more than one room
1060 var samebidcells = jQuery("td.vbo-checkinday[data-bids='"+bid+"']");
1061 if (samebidcells.length > 1) {
1062 mustReload = true;
1063 }
1064 //
1065 jQuery(newcellspool).each(function(k, v) {
1066 var cur_units_booked = parseInt(jQuery(cellspool[k]).find('a').parent('td').attr('data-units-booked'));
1067 if ((v.hasClass('busy') && v.attr('data-bids') != jQuery(cellspool[k]).attr('data-bids')) || isNaN(cur_units_booked) || cur_units_booked > 1) {
1068 mustReload = true;
1069 return false;
1070 }
1071 });
1072 if (mustReload !== true) {
1073 /* switch cells */
1074 var switchmap = [];
1075 jQuery(cellspool).each(function(k, v) {
1076 var switchcell = {
1077 hcont: v.html(),
1078 cl: v.attr('class'),
1079 bids: v.attr('data-bids'),
1080 tit: v.attr('title')
1081 };
1082 switchmap[k] = switchcell;
1083 v.html("").attr('class', 'notbusy').attr('data-bids', '').attr('title', '');
1084 });
1085 jQuery(switchmap).each(function(k, v) {
1086 jQuery(newcellspool[k]).html(v.hcont).attr('class', v.cl).attr('data-bids', v.bids).attr('title', v.tit);
1087 // re-bind hover event
1088 jQuery(newcellspool[k]).hover(function() {
1089 registerHoveringTooltip(this);
1090 }, unregisterHoveringTooltip);
1091 //
1092 });
1093 jQuery('td.vbo-dragging-cells-tmp').removeClass('vbo-dragging-cells-tmp');
1094 jQuery('td.vbo-dragged-cells-tmp').removeClass('vbo-dragged-cells-tmp');
1095 }
1096 if (obj_res.esit < 1) {
1097 //some errors occurred after executing certain functions
1098 if (obj_res.message.length) {
1099 alert(obj_res.message);
1100 }
1101 document.location.href='index.php?option=com_vikbooking&task=overv';
1102 } else {
1103 finalizeDndUpdate(mustReload, obj_res);
1104 }
1105 }
1106 },
1107 (err) => {
1108 alert("Request Failed");
1109 // restore the old cells
1110 jQuery('td.vbo-dragging-cells-tmp').removeClass('vbo-dragging-cells-tmp');
1111 jQuery('td.vbo-dragged-cells-tmp').removeClass('vbo-dragged-cells-tmp');
1112 //
1113 jQuery(".vbo-info-overlay-block").hide();
1114 }
1115 );
1116 }
1117
1118 /* DnD function to animate a success checkmark. The function may refresh the page once complete as this could be launched when there are multiple units for the rooms */
1119 function finalizeDndUpdate(mustReload, obj_res) {
1120 jQuery('#vbo-dnd-response').html(obj_res.message+' '+obj_res.vcm);
1121 jQuery('.vbo-loading-dnd-footer').hide();
1122 var start = 100;
1123 var mid = 145;
1124 var end = 250;
1125 var width = 22;
1126 var leftX = start;
1127 var leftY = start;
1128 var rightX = mid - (width / 2.7);
1129 var rightY = mid + (width / 2.7);
1130 var animationSpeed = 20;
1131 var closingdelay = 700;
1132 var ctx = document.getElementById('vbo-dnd-canvas-success').getContext('2d');
1133 ctx.lineWidth = width;
1134 ctx.strokeStyle = 'rgba(0, 150, 0, 1)';
1135 for (var i = start; i < mid; i++) {
1136 var drawLeft = window.setTimeout(function () {
1137 ctx.beginPath();
1138 ctx.moveTo(start, start);
1139 ctx.lineTo(leftX, leftY);
1140 ctx.stroke();
1141 leftX++;
1142 leftY++;
1143 }, 1 + (i * animationSpeed) / 3);
1144 }
1145 for (var i = mid; i < end; i++) {
1146 var drawRight = window.setTimeout(function () {
1147 ctx.beginPath();
1148 ctx.moveTo(leftX, leftY);
1149 ctx.lineTo(rightX, rightY);
1150 ctx.stroke();
1151 rightX++;
1152 rightY--;
1153 }, 1 + (i * animationSpeed) / 3);
1154 }
1155 //hide modal window
1156 window.setTimeout(function () {
1157 if (obj_res.vcm.length) {
1158 var vcmbtn = '<br clear="all"/><button type="button" class="btn btn-danger" onclick="'+(mustReload === true ? 'document.location.href=\'index.php?option=com_vikbooking&task=overv\'' : 'closeEsitDialog();')+'">' + Joomla.JText._('VBANNULLA') + '</button>';
1159 jQuery('#vbo-dnd-response').append(vcmbtn);
1160 } else {
1161 if (mustReload === true) {
1162 document.location.href='index.php?option=com_vikbooking&task=overv';
1163 } else {
1164 jQuery('.vbo-info-overlay-block').fadeOut(400, function(){
1165 //clear/reset canvas in case of previous drawing and response text
1166 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1167 jQuery('#vbo-dnd-response').html("");
1168 //
1169 });
1170 }
1171 }
1172 }, closingdelay + (i * animationSpeed) / 3);
1173 //
1174 }
1175
1176 /* DnD function that can be called by those with VCM that have disabled the automated updates. Simply closes the modal window */
1177 function closeEsitDialog() {
1178 jQuery('.vbo-info-overlay-block').fadeOut(400, function(){
1179 //clear/reset canvas in case of previous drawing and response text
1180 var ctx = document.getElementById('vbo-dnd-canvas-success').getContext('2d');
1181 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1182 jQuery('#vbo-dnd-response').html("");
1183 //
1184 });
1185 }
1186
1187 /* Hover Tooltip functions */
1188 function registerHoveringTooltip(that) {
1189 if (hovtip) {
1190 return false;
1191 }
1192 if (hovtimer) {
1193 clearTimeout(hovtimer);
1194 hovtimer = null;
1195 }
1196 var elem = jQuery(that);
1197 var cellheight = elem.outerHeight();
1198 var celldata = new Array();
1199 if (elem.hasClass('subroom-busy')) {
1200 celldata.push(elem.parent('tr').attr('data-subroomid'));
1201 celldata.push(elem.attr('data-day'));
1202 }
1203 hovtimer = setTimeout(() => {
1204 if (isdragging) {
1205 // prevent tooltip from popping up if dragging
1206 unregisterHoveringTooltip();
1207
1208 return;
1209 }
1210
1211 // turn flag on
1212 hovtip = true;
1213
1214 // set default content
1215 jQuery(
1216 "<div class=\"vbo-overview-tipblock\">" +
1217 "<div class=\"vbo-overview-tipinner\"><span class=\"vbo-overview-tiploading\">" + Joomla.JText._('VIKLOADING') + "</span></div>" +
1218 "<div class=\"vbo-overview-tipexpander\" style=\"display: none;\"><div class=\"vbo-overview-expandtoggle\"><i class=\"<?php echo VikBookingIcons::i('expand'); ?>\"></i></div></div>" +
1219 "</div>"
1220 ).appendTo(elem);
1221
1222 // set default bottom value to have the tooptip displayed above the cell
1223 jQuery(".vbo-overview-tipblock").css("bottom", "+=" + cellheight);
1224
1225 // load tooltip bookings
1226 loadTooltipBookings(elem.attr('data-bids'), celldata);
1227 }, 1500);
1228 }
1229
1230 function unregisterHoveringTooltip() {
1231 clearTimeout(hovtimer);
1232 hovtimer = null;
1233 }
1234
1235 function adjustHoveringTooltip() {
1236 setTimeout(() => {
1237 var ver_difflim = 35;
1238 var hor_difflim = 20;
1239 var tip_block = jQuery('.vbo-overview-tipblock');
1240 if (!tip_block || !tip_block.length) {
1241 return;
1242 }
1243 var table_wrap = tip_block.closest('.vbo-overv-montable-wrap');
1244
1245 if (tip_block.outerHeight() > table_wrap.outerHeight()) {
1246 // tooltip is too tall to fit in the table, render the modal instead
1247 tip_block.hide().closest('td.busy').trigger('click');
1248 return;
1249 }
1250
1251 // vertical positioning
1252 var otop = tip_block.offset().top;
1253 if (otop > 0 && otop < ver_difflim) {
1254 // adjust tooltip position
1255 tip_block.css('bottom', '-=' + (ver_difflim - otop));
1256 } else if (otop < table_wrap.offset().top) {
1257 // tooltip exceeds wrapping table, move it underneath the cell
1258 var extra_padding_top = 0;
1259 var tip_inner_padtop = tip_block.find('.vbo-overview-tipinner').css('padding-top');
1260 if (tip_inner_padtop) {
1261 // get only numbers
1262 extra_padding_top = tip_inner_padtop.replace(/[^0-9]/g, '');
1263 extra_padding_top = !isNaN(extra_padding_top) ? parseInt(extra_padding_top) : 0;
1264 }
1265 tip_block.css('bottom', 'auto').css('top', (tip_block.parent('td').outerHeight() - extra_padding_top));
1266 }
1267
1268 // horizontal positioning
1269 if (tip_block.offset().left < table_wrap.offset().left) {
1270 var left_diff = table_wrap.offset().left - tip_block.offset().left;
1271 if (left_diff > hor_difflim) {
1272 // tooltip exceeds table on the left, move it to the right of the cell (i.e 1st day of month)
1273 tip_block.css('right', tip_block.css('left'));
1274 tip_block.css('left', 'unset');
1275 }
1276 }
1277 }, 60);
1278 }
1279
1280 function hideVboTooltip() {
1281 jQuery('.vbo-overview-tipblock').remove();
1282 hovtip = false;
1283 }
1284
1285 function loadTooltipBookings(bids, celldata) {
1286 if (!bids || bids === undefined || !bids.length) {
1287 hideVboTooltip();
1288 return false;
1289 }
1290
1291 var subroomdata = celldata.length ? celldata[0] : '';
1292
1293 // perform the request
1294 VBOCore.doAjax(
1295 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=getbookingsinfo'); ?>",
1296 {
1297 tmpl: "component",
1298 idorders: bids,
1299 subroom: subroomdata
1300 },
1301 (res) => {
1302 try {
1303 var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
1304 jQuery('.vbo-overview-tiploading').remove();
1305 var container = jQuery('.vbo-overview-tipinner');
1306 jQuery(obj_res).each(function(k, v) {
1307 // get base navigation URIs
1308 var base_uri_details = jQuery('.vbo-basenavuri-details').attr('href');
1309 var base_uri_edit = jQuery('.vbo-basenavuri-edit').attr('href');
1310
1311 // build content
1312 var bcont = "<div class=\"vbo-overview-tip-bookingcont\">";
1313 bcont += "<div class=\"vbo-overview-tip-bookingcont-left\">";
1314 bcont += "<div class=\"vbo-overview-tip-bid\"><span class=\"vbo-overview-tip-lbl\">ID <span class=\"vbo-overview-tip-lbl-innerleft\"><a href=\"" + base_uri_edit.replace('%d', v.id) + "\"><i class=\"<?php echo VikBookingIcons::i('edit'); ?>\"></i></a></span></span><span class=\"vbo-overview-tip-cnt\">"+v.id+"</span></div>";
1315 bcont += "<div class=\"vbo-overview-tip-bstatus\"><span class=\"vbo-overview-tip-lbl\"><?php echo addslashes(JText::translate('VBPVIEWORDERSEIGHT')); ?></span><span class=\"vbo-overview-tip-cnt\"><div class=\"label "+(v.status == 'confirmed' ? 'label-success' : 'label-warning')+"\">"+v.status_lbl+"</div></span></div>";
1316 bcont += "<div class=\"vbo-overview-tip-bdate\"><span class=\"vbo-overview-tip-lbl\"><?php echo addslashes(JText::translate('VBPVIEWORDERSONE')); ?></span><span class=\"vbo-overview-tip-cnt\"><a href=\"" + base_uri_details.replace('%d', v.id) + "\">"+v.ts+"</a></span></div>";
1317 if (bctags_count > 0) {
1318 var bctag_title = '';
1319 var bctag_color = '#ffffff';
1320 if (v.colortag.hasOwnProperty('color')) {
1321 bctag_color = v.colortag.color;
1322 bctag_title = v.colortag.name;
1323 }
1324 bcont += "<div class=\"vbo-overview-tip-bctag-wrap\"><div class=\"vbo-overview-tip-bctag\" data-bid=\""+v.id+"\" data-ctagcolor=\""+bctag_color+"\" style=\"background-color: "+bctag_color+"; color: "+v.colortag.fontcolor+";\" title=\""+bctag_title+"\"><i class=\"vboicn-price-tags\"></i></div></div>";
1325 }
1326 bcont += "</div>";
1327 bcont += "<div class=\"vbo-overview-tip-bookingcont-right\">";
1328 bcont += "<div class=\"vbo-overview-tip-bcustomer\"><span class=\"vbo-overview-tip-lbl\"><?php echo addslashes(JText::translate('VBOCUSTOMER')); ?></span><span class=\"vbo-overview-tip-cnt\">"+v.cinfo+"</span></div>";
1329 if (v.roomsnum > 1) {
1330 bcont += "<div class=\"vbo-overview-tip-brooms\"><span class=\"vbo-overview-tip-lbl\">" + Joomla.JText._('VBEDITORDERROOMSNUM') + (parseInt(v.roomsnum) > 1 ? " (" + v.roomsnum + ")" : "") + "</span><span class=\"vbo-overview-tip-cnt\">" + v.room_names + "</span></div>";
1331 }
1332 bcont += "<div class=\"vbo-overview-tip-bguests\"><span class=\"vbo-overview-tip-lbl\">" + Joomla.JText._('VBDAYS') + (v.split_stay > 0 ? ' (' + Joomla.JText._('VBO_SPLIT_STAY') + ')' : '') + "</span><span class=\"vbo-overview-tip-cnt hasTooltip\" title=\"" + Joomla.JText._('VBPICKUPAT') + " " + v.checkin + " - " + Joomla.JText._('VBRELEASEAT') + " " + v.checkout + "\">" + v.days + ", " + Joomla.JText._('VBEDITORDERADULTS') + ": " + v.tot_adults + (v.tot_children > 0 ? ", " + Joomla.JText._('VBEDITORDERCHILDREN') + ": " + v.tot_children : "") + "</span></div>";
1333 if (v.hasOwnProperty('rindexes')) {
1334 for (var rindexk in v.rindexes) {
1335 if (v.rindexes.hasOwnProperty(rindexk)) {
1336 bcont += "<div class=\"vbo-overview-tip-brindexes\"><span class=\"vbo-overview-tip-lbl\">" + rindexk + "</span><span class=\"vbo-overview-tip-cnt\">" + v.rindexes[rindexk] + "</span></div>";
1337 }
1338 }
1339 }
1340 if (v.hasOwnProperty('channelimg')) {
1341 bcont += "<div class=\"vbo-overview-tip-bprovenience\"><span class=\"vbo-overview-tip-lbl\">" + Joomla.JText._('VBPVIEWORDERCHANNEL') + "</span><span class=\"vbo-overview-tip-cnt\">" + v.channelimg + "</span></div>";
1342 }
1343 if (v.hasOwnProperty('optindexes') && celldata.length) {
1344 var subroomids = celldata[0].split('-');
1345 bcont += "<div class=\"vbo-overview-tip-optindexes\"><span class=\"vbo-overview-tip-lbl\"> </span><span class=\"vbo-overview-tip-cnt\"><select onchange=\"vboMoveSubunit('" + v.id + "', '" + subroomids[0] + "', '" + subroomids[1] + "', this.value, '" + celldata[1] + "');\">" + v.optindexes + "</select></span></div>";
1346 }
1347 bcont += "<div class=\"vbo-overview-tip-bookingcont-total\">";
1348 bcont += "<div class=\"vbo-overview-tip-btot\"><span class=\"vbo-overview-tip-lbl\">" + Joomla.JText._('VBEDITORDERNINE') + "</span><span class=\"vbo-overview-tip-cnt\">" + vboMessages.currencySymb + " " + v.format_tot + "</span></div>";
1349 if (v.totpaid > 0.00) {
1350 bcont += "<div class=\"vbo-overview-tip-btot\"><span class=\"vbo-overview-tip-lbl\">" + Joomla.JText._('VBPEDITBUSYTOTPAID') + "</span><span class=\"vbo-overview-tip-cnt\">" + vboMessages.currencySymb + " " + v.format_totpaid + "</span></div>";
1351 }
1352 var getnotes = v.adminnotes;
1353 if (getnotes !== null && getnotes.length) {
1354 bcont += "<div class=\"vbo-overview-tip-notes\"><span class=\"vbo-overview-tip-lbl\"><span class=\"vbo-overview-tip-notes-inner\"><i class=\"vboicn-info hasTooltip\" title=\"" + getnotes + "\"></i></span></span></div>";
1355 }
1356 bcont += "</div>";
1357 bcont += "</div>";
1358 bcont += "</div>";
1359 container.append(bcont);
1360 jQuery('.vbo-overview-tipexpander').show();
1361 });
1362 // adjust the position so that it won't go under other contents
1363 adjustHoveringTooltip()
1364 //
1365 jQuery(".hasTooltip").tooltip();
1366 } catch(err) {
1367 // restore
1368 hideVboTooltip();
1369 // display error
1370 console.error('could not parse JSON response', err, res);
1371 alert('Could not parse JSON response');
1372 }
1373 },
1374 (err) => {
1375 // restore
1376 hideVboTooltip();
1377 // display error
1378 console.error(err);
1379 alert(err.responseText);
1380 }
1381 );
1382 }
1383
1384 /**
1385 * Set a sub-unit to a room booking. Triggered by the room-day bookings modal
1386 */
1387 function vboSetSubunit(bid, rid, orkey, rindex) {
1388 if (!(rindex + '').length) {
1389 return false;
1390 }
1391
1392 if (!confirm(Joomla.JText._('VBOFEATASSIGNUNIT') + rindex + '?')) {
1393 return false;
1394 }
1395
1396 // show loading
1397 VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
1398
1399 // make the request to set the room sub-unit
1400 VBOCore.doAjax(
1401 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.set_room_booking_subunit'); ?>",
1402 {
1403 bid: bid,
1404 rid: rid,
1405 orkey: orkey,
1406 rindex: rindex,
1407 tmpl: 'component'
1408 },
1409 (res) => {
1410 // dismiss the modal (no need to stop the loading)
1411 VBOCore.emitEvent('vbo-dismiss-modal-overv-rdaybookings');
1412
1413 try {
1414 var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
1415 if (!obj_res.hasOwnProperty('nights') || !Array.isArray(obj_res['nights']) || !obj_res['nights'].length) {
1416 // invalid response
1417 throw new Error('Invalid response');
1418 }
1419
1420 // check if cells can be occupied
1421 var sub_rows = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + rindex + '"]');
1422 if (!sub_rows || !sub_rows.length) {
1423 console.error('Could not find any room sub-unit row');
1424 return false;
1425 }
1426
1427 // loop through all sub-unit rows to match the cells for the nights affected
1428 sub_rows.each(function() {
1429 var sub_row = jQuery(this);
1430 obj_res['nights'].forEach((ymd, kindex) => {
1431 let sub_row_cell = sub_row.find('td[data-day="' + ymd + '"]');
1432 if (sub_row_cell.length) {
1433 sub_row_cell.removeClass('subnotbusy').addClass('subroom-busy');
1434 if (kindex == 0) {
1435 sub_row_cell.addClass('vbo-checkinday');
1436 }
1437 sub_row_cell.attr('data-bids', '-' + obj_res['bid'] + '-');
1438 if (!sub_row_cell.find('span.vbo-draggable-sp').length) {
1439 sub_row_cell.prepend('<span class="vbo-draggable-sp"><a class="vbo-overview-subredday">&bull;</a></span>');
1440 }
1441 }
1442 });
1443 });
1444 } catch(err) {
1445 console.error('could not parse JSON response', err, res);
1446 alert('Could not parse JSON response');
1447 }
1448 },
1449 (err) => {
1450 // stop loading
1451 VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
1452
1453 // log and display the error
1454 console.error(err);
1455 alert(err.responseText);
1456 }
1457 );
1458 }
1459
1460 /**
1461 * Move a subunit group of cells to another subroom row. Triggered by the Hover Tooltip, by the room-day bookings modal, or by DnD.
1462 */
1463 function vboMoveSubunit(bid, rid, old_rindex, new_rindex, dday) {
1464 if (!confirm(Joomla.JText._('VBOFEATASSIGNUNIT') + new_rindex.replace('#', '') + '?')) {
1465 return false;
1466 }
1467 // check if movement can be made
1468 var cur_tr = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + old_rindex + '"]');
1469 if (!cur_tr || !cur_tr.length) {
1470 console.error('could not find the parent row of the subunit cells');
1471 return false;
1472 }
1473 var maincell = cur_tr.find('td.subroom-busy[data-day="' + dday + '"]');
1474 if (!maincell || !maincell.length) {
1475 console.error('could not find the main cell of the subunit to move');
1476 return false;
1477 }
1478 var dest_tr = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + new_rindex + '"]');
1479 if (!dest_tr || !dest_tr.length) {
1480 console.error('could not find the destination row for the subunit cells');
1481 return false;
1482 }
1483 var targetbids = maincell.attr('data-bids');
1484 if (targetbids.indexOf('-' + bid + '-') < 0) {
1485 console.error('given bid does not match with cell bids', bid, targetbids);
1486 return false;
1487 }
1488 var firstcell = maincell;
1489 var loop = true;
1490 while (loop === true) {
1491 var prevsib = firstcell.prev();
1492 if (prevsib && prevsib.length && prevsib.attr('data-bids') && prevsib.attr('data-bids').indexOf('-' + bid + '-') >= 0) {
1493 firstcell = prevsib;
1494 } else {
1495 loop = false;
1496 }
1497 }
1498 // make sure to get the check-in day (first cell)
1499 dday = firstcell.attr('data-day');
1500 var destcell = dest_tr.find('td.subnotbusy[data-day="' + dday + '"]');
1501 if (!destcell || !destcell.length) {
1502 // first destination cell is occupied, check if swap is allowed
1503 if (dest_tr.find('td.subroom-busy[data-day="' + dday + '"]').length) {
1504 var landbids = dest_tr.find('td.subroom-busy[data-day="' + dday + '"]').attr('data-bids');
1505 return vboSwapRoomSubunits(bid, landbids.replaceAll('-', ''), rid, old_rindex, new_rindex, dday);
1506 }
1507 console.error('could not find the first free destination cell');
1508 return false;
1509 }
1510 // check if all dates are free before making movements. Redundant, but useful.
1511 var freedates = true;
1512 var copyfirstcell = firstcell;
1513 var copydday = dday;
1514 loop = true;
1515 while (loop === true) {
1516 var nextsib = copyfirstcell.next();
1517 if (nextsib && nextsib.length && nextsib.attr('data-bids') && nextsib.attr('data-bids').indexOf('-' + bid + '-') >= 0) {
1518 copyfirstcell = nextsib;
1519 copydday = copyfirstcell.attr('data-day');
1520 var copydestcell = dest_tr.find('td.subnotbusy[data-day="' + copydday + '"]');
1521 if (!copydestcell || !copydestcell.length) {
1522 console.error('could not find the next free destination cell for ' + copydday);
1523 freedates = false;
1524 loop = false;
1525 }
1526 } else {
1527 loop = false;
1528 }
1529 }
1530 if (freedates === false) {
1531 return false;
1532 }
1533
1534 // loading opacity
1535 jQuery('.vbo-overview-tipblock').css('opacity', '0.6');
1536
1537 // perform the request
1538 VBOCore.doAjax(
1539 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=switchRoomIndex'); ?>",
1540 {
1541 tmpl: "component",
1542 bid: bid,
1543 rid: rid,
1544 old_rindex: old_rindex,
1545 new_rindex: new_rindex
1546 },
1547 (res) => {
1548 if (res.indexOf('e4j.error') >= 0 ) {
1549 // console.log(res);
1550 alert(res.replace("e4j.error.", ""));
1551
1552 // restore loading opacity in container
1553 jQuery('.vbo-overview-tipblock').css('opacity', '1');
1554
1555 // abort
1556 return;
1557 }
1558
1559 // hide tooltip
1560 hideVboTooltip();
1561
1562 // loop for moving cells
1563 loop = true;
1564 while (loop === true) {
1565 // populate destination cell attributes, classes and content
1566 destcell.removeClass('subnotbusy').addClass('subroom-busy').attr('data-bids', firstcell.attr('data-bids'));
1567 if (firstcell.hasClass('vbo-checkinday')) {
1568 firstcell.removeClass('vbo-checkinday');
1569 destcell.addClass('vbo-checkinday');
1570 }
1571 firstcell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').appendTo(destcell);
1572
1573 // re-bind hover event for tooltip
1574 jQuery(destcell).hover(function() {
1575 registerHoveringTooltip(this);
1576 }, unregisterHoveringTooltip);
1577
1578 // clean up the previous cell from which we moved data
1579 firstcell.removeClass('subroom-busy').addClass('subnotbusy').attr('data-bids', '');
1580
1581 // check if we have a next iteration to perform
1582 var nextsib = firstcell.next();
1583 if (nextsib && nextsib.length && nextsib.attr('data-bids') && nextsib.attr('data-bids').indexOf('-' + bid + '-') >= 0) {
1584 // prepare data for the next iteration
1585 firstcell = nextsib;
1586 dday = firstcell.attr('data-day');
1587 destcell = dest_tr.find('td.subnotbusy[data-day="' + dday + '"]');
1588 if (!destcell || !destcell.length) {
1589 console.error('could not find the next free destination cell');
1590 loop = false;
1591 }
1592 } else {
1593 // all cells were moved, break the loop
1594 loop = false;
1595 // check if we need to move the checkout snake as well
1596 if (nextsib && nextsib.find('.vbo-tableaux-booking-checkout').length) {
1597 dday = nextsib.attr('data-day');
1598 destcell = dest_tr.find('td.subnotbusy[data-day="' + dday + '"]');
1599 if (!destcell || !destcell.length) {
1600 // the landing checkout snake may be on an occupied date
1601 destcell = dest_tr.find('td.subroom-busy[data-day="' + dday + '"]');
1602 }
1603 if (destcell && destcell.length) {
1604 // prepend the checkout snake to the next cell
1605 nextsib.find('.vbo-tableaux-booking-checkout').prependTo(destcell);
1606 }
1607 }
1608 }
1609 }
1610 },
1611 (err) => {
1612 alert("Request Failed");
1613 // restore loading opacity in container
1614 jQuery('.vbo-overview-tipblock').css('opacity', '1');
1615 }
1616 );
1617
1618 return true;
1619 }
1620
1621 /**
1622 * Swap room sub-unit with another sub-unit on an occupied date. Triggered by DnD.
1623 */
1624 function vboSwapRoomSubunits(bid, bidtwo, rid, old_rindex, new_rindex, dday) {
1625 if (!confirm(Joomla.JText._('VBO_CONF_SWAP_RNUMB').replace('%s', old_rindex).replace('%s', new_rindex))) {
1626 return false;
1627 }
1628 if (!bid || !bidtwo || !rid || !old_rindex || !new_rindex || !dday) {
1629 console.error('Missing required arguments', bid, bidtwo, rid, old_rindex, new_rindex, dday);
1630 return false;
1631 }
1632 // check if movement can be made
1633 var cur_tr = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + old_rindex + '"]');
1634 if (!cur_tr || !cur_tr.length) {
1635 console.error('could not find the parent row of the subunit cells');
1636 return false;
1637 }
1638 var maincell = cur_tr.find('td.subroom-busy[data-day="' + dday + '"]');
1639 if (!maincell || !maincell.length) {
1640 console.error('could not find the main cell of the subunit to move');
1641 return false;
1642 }
1643 var dest_tr = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + new_rindex + '"]');
1644 if (!dest_tr || !dest_tr.length) {
1645 console.error('could not find the destination row for the subunit cells');
1646 return false;
1647 }
1648 var destcell = dest_tr.find('td.subroom-busy[data-day="' + dday + '"]');
1649 if (!destcell || !destcell.length) {
1650 console.error('could not find the destination cell of the subunit to move');
1651 return false;
1652 }
1653
1654 // gather the cells for the swop
1655 var swap_cells_one = [];
1656 var swap_cells_two = [];
1657 var swap_dates_one = [];
1658 var swap_dates_two = [];
1659 var swap_cell_from = maincell;
1660 var swap_cell_to = destcell;
1661
1662 // register the elements to swap of the first sub-unit
1663 while (true) {
1664 if (!swap_cell_from || !swap_cell_from.length) {
1665 // next cell not found
1666 break;
1667 }
1668 var swap_bids = swap_cell_from.attr('data-bids');
1669 if (!swap_bids || swap_bids.indexOf('-' + bid + '-') < 0) {
1670 // last occupied cell reached
1671 break;
1672 }
1673 // push cloned cell data to move
1674 swap_cells_one.push(swap_cell_from.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').clone(true));
1675 swap_dates_one.push(swap_cell_from.attr('data-day'));
1676 // set next iteration cell
1677 swap_cell_from = swap_cell_from.next();
1678 }
1679
1680 // register the elements to swap of the second sub-unit
1681 while (true) {
1682 if (!swap_cell_to || !swap_cell_to.length) {
1683 // next cell not found
1684 break;
1685 }
1686 var swap_bids = swap_cell_to.attr('data-bids');
1687 if (!swap_bids || swap_bids.indexOf('-' + bidtwo + '-') < 0) {
1688 // last occupied cell reached
1689 break;
1690 }
1691 // make sure we are swapping with the check-in day, or if it's a stay-date we need to unshift until the check-in
1692 if (!swap_cells_two.length && !swap_cell_to.hasClass('vbo-checkinday')) {
1693 // prepend first the cells until the check-in or until not found
1694 var prev_cell = swap_cell_to;
1695 while (true) {
1696 prev_cell = prev_cell.prev();
1697 if (!prev_cell || !prev_cell.length) {
1698 break;
1699 }
1700 var prev_cell_snake = prev_cell.find('.vbo-tableaux-booking').not('.vbo-tableaux-booking-checkout');
1701 if (prev_cell_snake && prev_cell_snake.length && (prev_cell_snake.hasClass('vbo-tableaux-booking-checkin') || prev_cell_snake.hasClass('vbo-tableaux-booking-stay'))) {
1702 // prepend cloned cell data to move
1703 swap_cells_two.unshift(prev_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').clone(true));
1704 swap_dates_two.unshift(prev_cell.attr('data-day'));
1705 } else {
1706 // no more previous cells
1707 break;
1708 }
1709 }
1710 }
1711 // push cloned cell data to move
1712 swap_cells_two.push(swap_cell_to.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').clone(true));
1713 swap_dates_two.push(swap_cell_to.attr('data-day'));
1714 // set next iteration cell
1715 swap_cell_to = swap_cell_to.next();
1716 }
1717
1718 if (!swap_cells_one.length || !swap_cells_two.length) {
1719 console.error('could not gather the cells to swap', swap_cells_one, swap_cells_two);
1720 return false;
1721 }
1722
1723 // walk over the cells of the first room to swap and check the corresponding destination
1724 var check_destcell = destcell;
1725 for (let i = 0; i < swap_cells_one.length; i++) {
1726 if (!check_destcell || !check_destcell.length) {
1727 // this cell does not exist, abort
1728 console.error('destination cell to #' + (i + 1) + ' was not found');
1729 return false;
1730 }
1731 var dest_bids = check_destcell.attr('data-bids');
1732 if (dest_bids && dest_bids.length && dest_bids.indexOf('-' + bidtwo + '-') < 0) {
1733 // this cell is already occupied, abort
1734 console.error('destination cell to #' + (i + 1) + ' is occupied by another reservation');
1735 return false;
1736 }
1737 // set next destination cell
1738 check_destcell = check_destcell.next();
1739 }
1740
1741 // walk over the cells of the second room to swap and check the corresponding destination
1742 check_destcell = maincell;
1743 for (let i = 0; i < swap_cells_two.length; i++) {
1744 if (!check_destcell || !check_destcell.length) {
1745 // this cell does not exist, abort
1746 console.error('destination cell from #' + (i + 1) + ' was not found');
1747 return false;
1748 }
1749 var dest_bids = check_destcell.attr('data-bids');
1750 if (dest_bids && dest_bids.length && dest_bids.indexOf('-' + bid + '-') < 0) {
1751 // this cell is already occupied, abort
1752 console.error('destination cell from #' + (i + 1) + ' is occupied by another reservation');
1753 return false;
1754 }
1755 // set next destination cell
1756 check_destcell = check_destcell.next();
1757 }
1758
1759 // make the AJAX request and swap cells only in case of success
1760 VBOCore.doAjax(
1761 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.swap_room_subunits'); ?>",
1762 {
1763 bid_one: bid,
1764 bid_two: bidtwo,
1765 rid: rid,
1766 index_one: old_rindex,
1767 index_two: new_rindex,
1768 checkin: dday,
1769 tmpl: 'component'
1770 },
1771 (res) => {
1772 // move the cells of the first sub-unit onto the cells of the second sub-unit
1773 var target_cell = destcell;
1774 var current_cell = maincell;
1775 var swap_diff_dates = false;
1776 for (let counter = 0; counter < swap_cells_one.length; counter++) {
1777 // remove elements from target
1778 target_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
1779 // swap cell elements
1780 swap_cells_one[counter].appendTo(target_cell);
1781 // set proper bids attribute
1782 target_cell.attr('data-bids', '-' + bid + '-');
1783 // check if we are swapping with a non check-in day sub-unit occupied cell
1784 if (counter === 0 && !target_cell.hasClass('vbo-checkinday')) {
1785 target_cell.addClass('vbo-checkinday');
1786 swap_diff_dates = true;
1787 } else if (counter > 0 && !target_cell.hasClass('subroom-busy')) {
1788 target_cell.removeClass('subnotbusy').addClass('subroom-busy');
1789 }
1790 // check if the reservations have different nights of stay
1791 var cell_will_occupy = true;
1792 if (counter > 0 && swap_diff_dates && !swap_dates_two.includes(swap_dates_one[counter])) {
1793 // the swapped unit will not occupy this cell, so clean it
1794 cell_will_occupy = false;
1795 } else if ((counter + 1) > swap_cells_two.length) {
1796 // this reservation is longer than the other
1797 cell_will_occupy = false;
1798 }
1799 if (!cell_will_occupy) {
1800 // remove the children elements from this longer reservation
1801 current_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
1802 // remove the occupied class as well
1803 current_cell.removeClass('subroom-busy').addClass('subnotbusy').attr('data-bids', '');
1804 }
1805 // set next target cell
1806 target_cell = target_cell.next();
1807 // update current cell
1808 current_cell = current_cell.next();
1809 }
1810
1811 // move the checkout snake of the first sub-unit
1812 if (target_cell && target_cell.length && current_cell && current_cell.length && current_cell.find('.vbo-tableaux-booking-checkout').length) {
1813 current_cell.find('.vbo-tableaux-booking-checkout').prependTo(target_cell);
1814 }
1815
1816 // move the cells of the second sub-unit onto the cells of the first sub-unit
1817 target_cell = maincell;
1818 current_cell = destcell;
1819 // check if we are swapping with a non check-in day sub-unit occupied cell
1820 if (swap_diff_dates) {
1821 var prev_land_cell = current_cell;
1822 while (true) {
1823 prev_land_cell = prev_land_cell.prev();
1824 if (!prev_land_cell || !prev_land_cell.length) {
1825 break;
1826 }
1827 if (prev_land_cell.hasClass('subroom-busy')) {
1828 // overwrite first cell to move and to land
1829 current_cell = prev_land_cell;
1830 target_cell = target_cell.prev();
1831 // empty this cell because no one will occupy it
1832 current_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
1833 // make the cell free
1834 current_cell.removeClass('subroom-busy').addClass('subnotbusy').attr('data-bids', '');
1835 }
1836 if (prev_land_cell.hasClass('vbo-checkinday')) {
1837 // we have reached the first cell of this booking
1838 break;
1839 }
1840 }
1841 }
1842 for (let counter = 0; counter < swap_cells_two.length; counter++) {
1843 // remove elements from target
1844 target_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
1845 // swap cell elements
1846 swap_cells_two[counter].appendTo(target_cell);
1847 // set proper bids attribute
1848 target_cell.attr('data-bids', '-' + bidtwo + '-');
1849 // check if we are swapping with a non check-in day sub-unit occupied cell
1850 if (counter === 0 && !target_cell.hasClass('vbo-checkinday')) {
1851 target_cell.addClass('vbo-checkinday');
1852 if (current_cell.hasClass('vbo-checkinday') && !swap_dates_one.includes(swap_dates_two[counter])) {
1853 current_cell.removeClass('vbo-checkinday');
1854 }
1855 } else if (counter > 0 && target_cell.hasClass('vbo-checkinday')) {
1856 target_cell.removeClass('vbo-checkinday');
1857 }
1858 if (!target_cell.hasClass('subroom-busy')) {
1859 target_cell.removeClass('subnotbusy').addClass('subroom-busy');
1860 }
1861 // check if the reservations have different nights of stay
1862 if ((counter + 1) > swap_cells_one.length) {
1863 // remove the children elements from this longer reservation
1864 current_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
1865 // remove the occupied class as well
1866 current_cell.removeClass('subroom-busy').addClass('subnotbusy').attr('data-bids', '');
1867 // set the occupied class onto the target
1868 target_cell.removeClass('subnotbusy').addClass('subroom-busy');
1869 }
1870 // set next target cell
1871 target_cell = target_cell.next();
1872 // update current cell
1873 current_cell = current_cell.next();
1874 }
1875
1876 // move the checkout snake of the second sub-unit
1877 if (target_cell && target_cell.length && current_cell && current_cell.length && current_cell.find('.vbo-tableaux-booking-checkout').length) {
1878 // make sure to move just the first check-out snake, because snakes with equal duration may have moved the check-out here already
1879 current_cell.find('.vbo-tableaux-booking-checkout').first().prependTo(target_cell);
1880 }
1881 },
1882 (err) => {
1883 alert(err.responseText);
1884 console.error(err.responseText);
1885 }
1886 );
1887
1888 return true;
1889 }
1890
1891 /**
1892 * Open a booking in a new tab.
1893 */
1894 function vboOvervOpenBooking(bid) {
1895 var open_url = jQuery('.vbo-basenavuri-details').attr('href');
1896 open_url = open_url.replace('%d', bid);
1897 // navigate in a new tab
1898 window.open(open_url, '_blank');
1899 }
1900
1901 /**
1902 * DOM ready state
1903 */
1904 jQuery(function() {
1905 // register to the event emitted when a new booking is created through an admin widget
1906 document.addEventListener('vbo_new_booking_created', (e) => {
1907 if (!e || !e.detail || !e.detail.hasOwnProperty('bid') || !e.detail['bid']) {
1908 // do nothing
1909 return;
1910 }
1911 // reload the page to display the new booking just created
1912 location.reload();
1913 });
1914
1915 /* calculate padding to increase draggable area and avoid "display: table" for the parent TD and "display: table-cell" for the draggable SPAN with 100% width and height, middle aligns */
1916 jQuery("td.vbo-checkinday span.vbo-draggable-sp").each(function(k, v) {
1917 var parentheight = jQuery(this).closest('td').height();
1918 var spheight = jQuery(this).height();
1919 var padsp = Math.floor((parentheight - spheight) / 2);
1920 jQuery(this).css({"padding": padsp + "px 0"});
1921 });
1922
1923 /* Expand/Collapse tooltip */
1924 jQuery(document.body).on("click", ".vbo-overview-expandtoggle", function() {
1925 jQuery(this).closest('.vbo-overview-tipblock').toggleClass('vbo-overview-tipblock-expanded');
1926 });
1927
1928 /* DnD Start */
1929
1930 /* DnD Event dragstart */
1931 jQuery(document.body).on("dragstart", "td.vbo-checkinday span, td.vbo-checkinday .vbo-tableaux-booking-checkin", function(e) {
1932 // start dragging and prevent tooltip from popping up
1933 isdragging = true;
1934 if (hovtip === true) {
1935 hideVboTooltip();
1936 }
1937
1938 var parentcell = jQuery(this).closest('td');
1939 if (parentcell.hasClass('busytmplock') || parentcell.hasClass('busy-sharedcalendar')) {
1940 return false;
1941 }
1942
1943 // reset pool of booked date cells
1944 cellspool = [];
1945 var dt = e.originalEvent.dataTransfer;
1946 var bidstart = parentcell.attr('data-bids');
1947 var checkind = parentcell.attr('data-day');
1948 var roomid = parentcell.parent('tr').find('td').first().attr('data-roomid');
1949 if (!bidstart.length || !checkind.length || !roomid.length) {
1950 return false;
1951 }
1952
1953 // count booking nights and set booked date cells
1954 var totnights = countBookingCells(parentcell, bidstart, roomid);
1955
1956 // add dragging class
1957 if (jQuery(this).find('.vbo-tableaux-guest-name').length) {
1958 jQuery(this).find('.vbo-tableaux-guest-name').addClass('vbo-dragging-sp');
1959 } else {
1960 if (!jQuery(this).hasClass('vbo-tableaux-booking-avatar')) {
1961 jQuery(this).addClass('vbo-dragging-sp');
1962 }
1963 }
1964
1965 // populate dataTransfer data
1966 var parent_rid = '';
1967 var subunit_id = '';
1968 if (parentcell.hasClass('subroom-busy')) {
1969 var subdata = parentcell.parent('tr').attr('data-subroomid').split('-');
1970 parent_rid = subdata[0];
1971 subunit_id = subdata[1];
1972 }
1973 dt.setData('Checkin', checkind);
1974 dt.setData('Roomid', roomid);
1975 dt.setData('Bid', bidstart);
1976 dt.setData('Nights', totnights);
1977 dt.setData('Parentrid', parent_rid);
1978 dt.setData('Subunit', subunit_id);
1979 });
1980
1981 /* DnD Events dragenter dragover drop */
1982 jQuery(document.body).on("dragenter dragover drop", "td.notbusy, td.busy, td.subnotbusy, td.subroom-busy", function(e) {
1983 // always prevent default
1984 e.preventDefault();
1985
1986 // gather dragging cell data
1987 var dt = e.originalEvent.dataTransfer;
1988 var checkind = dt.getData('Checkin');
1989 var roomid = dt.getData('Roomid');
1990 var bid = dt.getData('Bid');
1991 var totnights = dt.getData('Nights');
1992 var parent_rid = dt.getData('Parentrid');
1993 var subunit_id = dt.getData('Subunit');
1994
1995 // check if we are hovering something that requires styling
1996 if (e.type === 'dragover') {
1997 var hover_cell = jQuery(this);
1998 if (hover_cell.hasClass('subroom-busy') && hover_cell.hasClass('vbo-checkinday')) {
1999 if (hover_cell.attr('data-day') != checkind && totnights > 1) {
2000 hover_cell.find('.vbo-tableaux-booking-checkin').addClass('vbo-droppable-cell');
2001 }
2002 }
2003 // do not proceed any further
2004 return;
2005 }
2006
2007 // only look for drop
2008 if (e.type !== 'drop') {
2009 // ignore if we are not dropping the element
2010 return;
2011 }
2012
2013 // always reset pool of cells
2014 newcellspool = [];
2015
2016 /* check if drop is allowed */
2017 var landrow = jQuery(this).closest('tr');
2018 var landrid = landrow.find('td').first().attr('data-roomid');
2019 var landbids = jQuery(this).attr('data-bids');
2020 if (landrid === undefined || !landrid.length) {
2021 return false;
2022 }
2023
2024 if (jQuery(this).hasClass('busy') && !jQuery(this).hasClass('vbo-partially')) {
2025 if (bid != landbids) {
2026 // landed on an occupied date but not for the same booking ID so drop is not allowed on this day
2027 return false;
2028 }
2029 }
2030
2031 // build pool of cells
2032 var freenights = countCellFreeNights(jQuery(this), landrid, totnights, bid);
2033 if (freenights < totnights) {
2034 alert(Joomla.JText._('VBOVWDNDERRNOTENCELLS').replace('%s', freenights).replace('%d', totnights));
2035 return false;
2036 }
2037
2038 // collect the new dates
2039 var nowdatefrom = jQuery(newcellspool[0]).attr('data-day');
2040 var nowdateto = jQuery(newcellspool[(newcellspool.length -1)]).attr('data-day');
2041
2042 // check if we are just moving a sub-unit onto another for the same dates
2043 if (jQuery(this).hasClass('subnotbusy') || jQuery(this).hasClass('subroom-busy')) {
2044 // dropping on a sub-unit is only allowed onto the same room ID, for the same dates (switch room index only)
2045 var subdata = landrow.attr('data-subroomid').split('-');
2046 if (!subdata || !parent_rid || !subunit_id || parent_rid != subdata[0] || !subdata[1] || subunit_id == subdata[1]) {
2047 // display proper error
2048 if (subdata[1] && subunit_id == subdata[1]) {
2049 // must use the same dates for sub-units
2050 alert(Joomla.JText._('VBO_DRAG_SUBUNITS_SAMEDATE'));
2051 } else {
2052 // only sub-units can be moved onto rows for sub-units
2053 alert(Joomla.JText._('VBO_ERR_SUBUN_MOVE_SUBUN'));
2054 }
2055 return false;
2056 }
2057
2058 // sub-unit drag&drop is only allowed on the same date to change sub-unit index
2059 if (nowdatefrom != checkind) {
2060 alert(Joomla.JText._('VBO_DRAG_SUBUNITS_SAMEDATE'));
2061 return false;
2062 }
2063
2064 // attempt to perform the switch-room-index request
2065 if (jQuery(this).hasClass('subnotbusy')) {
2066 // move sub-unit to a different index
2067 if (!vboMoveSubunit(bid.replaceAll('-', ''), parent_rid, subunit_id, subdata[1], checkind)) {
2068 return false;
2069 }
2070 } else {
2071 // dropped on an occupied sub-unit, so ask for swap confirmation
2072 if (!vboSwapRoomSubunits(bid.replaceAll('-', ''), landbids.replaceAll('-', ''), parent_rid, subunit_id, subdata[1], checkind)) {
2073 return false;
2074 }
2075 }
2076
2077 // do not proceed
2078 return;
2079 }
2080
2081 /* populate temporary class for the new cells */
2082 jQuery(cellspool).each(function(k, v) {
2083 v.addClass('vbo-dragging-cells-tmp');
2084 });
2085
2086 // bookings for multiple rooms should add the same dragging class also to the booking for the other rooms
2087 var samebidcells = jQuery("td.vbo-checkinday[data-bids='" + bid + "']");
2088 if (samebidcells.length > 1) {
2089 jQuery("td[data-bids='" + bid + "']").each(function(k, v) {
2090 if (!jQuery(v).hasClass('vbo-dragging-cells-tmp')) {
2091 jQuery(v).addClass('vbo-dragging-cells-tmp');
2092 }
2093 });
2094 }
2095 jQuery(newcellspool).each(function(k, v) {
2096 v.addClass('vbo-dragged-cells-tmp');
2097 });
2098
2099 /* populate and showing loading message */
2100 jQuery('.vbo-loading-dnd-head').text(Joomla.JText._('VBOVWDNDMOVINGBID').replace('%d', bid));
2101 var movingmess = '';
2102 if (roomid != landrid) {
2103 movingmess += Joomla.JText._('VBOVWDNDMOVINGROOM').replace('%s', jQuery("td[data-roomid='"+landrid+"']").first().find('span.vbo-overview-roomname').text() );
2104 }
2105 if (nowdatefrom != jQuery(cellspool[0]).attr('data-day') || nowdateto != jQuery(cellspool[(cellspool.length -1)]).attr('data-day')) {
2106 if (roomid != landrid) {
2107 movingmess += ', ';
2108 }
2109 movingmess += Joomla.JText._('VBOVWDNDMOVINGDATES').replace('%s', nowdatefrom + ' - ' + nowdateto);
2110 }
2111
2112 // hide tooltip, if any
2113 hideVboTooltip();
2114
2115 // display moving message
2116 jQuery('.vbo-loading-dnd-body').text(movingmess);
2117 jQuery('.vbo-info-overlay-block').fadeIn();
2118 jQuery('.vbo-loading-dnd-footer').show();
2119
2120 /* fire the Ajax request after 1.5 seconds just for giving visibility to the loading message */
2121 setTimeout(function() {
2122 doAlterBooking(bid, roomid, landrid);
2123 }, 1500);
2124 });
2125
2126 /* DnD Event dragend: remove class, attribute and dataTransfer Data if drag ends on the same position as dragstart or onto an invalid date */
2127 jQuery(document.body).on("dragend", "td.vbo-checkinday span, td.vbo-checkinday .vbo-tableaux-booking-checkin", function(e) {
2128 // stop dragging to restore tooltip functions
2129 isdragging = false;
2130 // we could also access the originally dragged cell
2131 var dt = e.originalEvent.dataTransfer;
2132 // restore temporary classes
2133 jQuery('.vbo-dragging-sp').removeClass('vbo-dragging-sp');
2134 jQuery('.vbo-droppable-cell').removeClass('vbo-droppable-cell');
2135 });
2136 /* DnD End */
2137
2138 /* Show New Reservation Form & Modal with bookings - Start */
2139 jQuery('td.busy, td.notbusy').dblclick(function() {
2140 var curday = jQuery(this).attr('data-day');
2141 var roomid = jQuery(this).parent('tr').find('td.roomname').attr('data-roomid');
2142 roomid = roomid && roomid.length ? roomid : '';
2143 last_room_click = '';
2144 last_date_click = '';
2145 next_date_click = '';
2146 vboCallOpenJModal('vbo-new-res', 'index.php?option=com_vikbooking&task=calendar&cid[]='+roomid+'&checkin='+curday+'&overv=1&tmpl=component');
2147 });
2148
2149 jQuery(document.body).on('click', 'td.busy, td.notbusy, td.busytmplock, td.subroom-busy', function(e) {
2150 if (jQuery(this).hasClass('vbo-widget-booskcal-cell-mday')) {
2151 return;
2152 }
2153
2154 if (jQuery(this).hasClass('busy') || jQuery(this).hasClass('busytmplock') || jQuery(this).hasClass('subroom-busy')) {
2155 // make sure the clicked target is not inside the tooltip
2156 if (e && e.target) {
2157 if (jQuery(e.target).closest('.vbo-overview-tipblock').length) {
2158 // abort when click originated from the tooltip
2159 return;
2160 }
2161 if (jQuery(e.target).is('a')) {
2162 // abort when click is made on a link (View choosebusy)
2163 return;
2164 }
2165 if (jQuery(e.target).hasClass('vbo-roomdaynote-display') || jQuery(e.target).hasClass('vbo-roomdaynote-trigger')) {
2166 // abort when click is made on a room-day note
2167 return;
2168 }
2169 }
2170
2171 // trigger mouseleave event to prevent tooltip from showing
2172 jQuery(this).trigger('mouseleave');
2173
2174 // get room name, date and day-bids
2175 var room_name = '';
2176 var sub_room_data = '';
2177 var room_id = 0;
2178 if (jQuery(this).hasClass('subroom-busy')) {
2179 sub_room_data = jQuery(this).parent('tr').attr('data-subroomid');
2180 room_id = sub_room_data.split('-')[0];
2181 room_name = jQuery('td.roomname[data-roomid="' + room_id + '"]').not('.subroomname').first().find('.vbo-overview-roomname').text();
2182 } else {
2183 var main_room_cell = jQuery(this).parent('tr').find('td.roomname');
2184 room_id = main_room_cell.attr('data-roomid');
2185 room_name = main_room_cell.find('.vbo-overview-roomname').text();
2186 }
2187 var date_ymd = jQuery(this).attr('data-day');
2188 var date_read = jQuery('.bluedays[data-ymd="' + date_ymd + '"]').attr('data-readymd');
2189 var date_bids = jQuery(this).attr('data-bids');
2190 var def_bicon = '<?php VikBookingIcons::e('user', 'vbo-dashboard-guest-activity-avatar-icon'); ?>';
2191 var closure_i = '<?php VikBookingIcons::e('ban', 'vbo-dashboard-guest-activity-avatar-icon'); ?>';
2192
2193 // display modal with booking details
2194 var rday_bookings_modal_body = VBOCore.displayModal({
2195 suffix: 'overv-rdaybookings',
2196 extra_class: 'vbo-modal-rounded vbo-modal-tall vbo-modal-nofooter',
2197 title: room_name + (sub_room_data ? ' #' + sub_room_data.split('-')[1] : '') + ' - ' + date_read,
2198 draggable: true,
2199 dismiss_event: 'vbo-dismiss-modal-overv-rdaybookings',
2200 loading_event: 'vbo-loading-modal-overv-rdaybookings',
2201 loading_body: '<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>',
2202 });
2203
2204 // show loading
2205 VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
2206
2207 // make the request to get the bookings information
2208 VBOCore.doAjax(
2209 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=getbookingsinfo'); ?>",
2210 {
2211 status: 'any',
2212 idroom: room_id,
2213 stay_date: date_ymd,
2214 idorders: date_bids,
2215 subroom: sub_room_data,
2216 sharedcal: (jQuery(this).hasClass('busy-sharedcalendar') ? 1 : 0),
2217 tmpl: 'component'
2218 },
2219 (res) => {
2220 // stop loading
2221 VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
2222 try {
2223 var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
2224
2225 // build the HTML response nodes
2226 var rday_bookings_wrap = jQuery('<div></div>').addClass('vbo-dashboard-guests-latest');
2227 var rday_bookings_list = jQuery('<div></div>').addClass('vbo-dashboard-guest-messages-list');
2228
2229 // flag to indicate if the button to toggle the cancelled reservations was displayed
2230 var show_canc_res_flag = false;
2231
2232 // loop through all bookings
2233 for (var b in obj_res) {
2234 if (!obj_res.hasOwnProperty(b)) {
2235 continue;
2236 }
2237 // nights and guests
2238 var nights_guests = [
2239 obj_res[b]['roomsnum'] + ' ' + Joomla.JText._((obj_res[b]['roomsnum'] > 1 ? 'VBPVIEWORDERSTHREE' : 'VBEDITORDERTHREE')),
2240 obj_res[b]['days'] + ' ' + Joomla.JText._((obj_res[b]['days'] > 1 ? 'VBDAYS' : 'VBDAY')),
2241 obj_res[b]['tot_adults'] + ' ' + Joomla.JText._((obj_res[b]['tot_adults'] > 1 ? 'VBMAILADULTS' : 'VBMAILADULT'))
2242 ];
2243 if (obj_res[b]['tot_children'] > 0) {
2244 nights_guests.push(obj_res[b]['tot_children'] + ' ' + Joomla.JText._((obj_res[b]['tot_children'] > 1 ? 'VBMAILCHILDREN' : 'VBMAILCHILD')));
2245 }
2246
2247 // OTA booking ID
2248 var ota_bid_info = '';
2249 if (obj_res[b].hasOwnProperty('idorderota') && obj_res[b].hasOwnProperty('channel') && obj_res[b]['idorderota'] && obj_res[b]['channel']) {
2250 if (obj_res[b]['idorderota'].length <= 16) {
2251 // try to display the information only for API channels
2252 ota_bid_info = '<span class="label label-info">' + obj_res[b]['idorderota'] + '</span> ';
2253 }
2254 }
2255
2256 // the separator between confirmed and cancelled reservations
2257 var res_type_separator = null;
2258
2259 // booking status and badge
2260 var badge_type = 'warning';
2261 if (obj_res[b]['status'] == 'confirmed') {
2262 badge_type = 'success';
2263 } else if (obj_res[b]['status'] == 'cancelled') {
2264 badge_type = 'danger';
2265 if (!show_canc_res_flag) {
2266 // build the separator between confirmed and cancelled reservations to toggle the latter ones
2267 res_type_separator = jQuery('<div></div>').addClass('vbo-bookings-status-separator');
2268 var btn_separator = jQuery('<button></button>').attr('type', 'button').addClass('btn btn-small btn-secondary').text(Joomla.JText._('VBO_SHOW_CANCELLATIONS'));
2269 btn_separator.on('click', function() {
2270 jQuery(this).closest('.vbo-dashboard-guest-messages-list').find('[data-type="cancelled"]').toggle();
2271 });
2272 res_type_separator.append(btn_separator);
2273 // turn flag on at the very first cancelled reservation found
2274 show_canc_res_flag = true;
2275 }
2276 }
2277
2278 // build main booking node
2279 var rday_booking = jQuery('<div></div>').addClass('vbo-dashboard-guest-activity vbo-w-guestmessages-message');
2280 if (badge_type == 'danger') {
2281 // hide cancelled booking by default and set proper attribute
2282 rday_booking.attr('data-type', 'cancelled').css('display', 'none');
2283 }
2284 rday_booking.attr('data-idorder', obj_res[b]['id']);
2285 rday_booking.on('click', function(e) {
2286 var click_target = jQuery(e.target);
2287 if (click_target.is('a') || click_target.is('select') || click_target.is('option')) {
2288 return false;
2289 }
2290 if ((click_target.is('span') && click_target.hasClass('label')) || click_target.is('img') || click_target.is('i')) {
2291 // open booking details within the apposite admin-widget
2292 e.stopPropagation();
2293 VBOCore.handleDisplayWidgetNotification({
2294 widget_id: 'booking_details',
2295 }, {
2296 booking_id: jQuery(this).attr('data-idorder'),
2297 });
2298 } else {
2299 // open booking details on a new tab
2300 vboOvervOpenBooking(jQuery(this).attr('data-idorder'));
2301 }
2302 });
2303
2304 // build booking structure
2305 var rday_booking_html = '';
2306 rday_booking_html += '<div class="vbo-dashboard-guest-activity-avatar">' + "\n";
2307 if (obj_res[b]['avatar_src']) {
2308 rday_booking_html += '<img class="vbo-dashboard-guest-activity-avatar-profile" src="' + obj_res[b]['avatar_src'] + '" alt="' + obj_res[b]['avatar_alt'] + '" />' + "\n";
2309 } else if (obj_res[b]['closure']) {
2310 rday_booking_html += closure_i + "\n";
2311 } else {
2312 rday_booking_html += def_bicon + "\n";
2313 }
2314 rday_booking_html += '</div>' + "\n";
2315 rday_booking_html += '<div class="vbo-dashboard-guest-activity-content">' + "\n";
2316 rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-head">' + "\n";
2317 rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-info-details">' + "\n";
2318 rday_booking_html += ' <h4 class="vbo-w-guestmessages-message-gtitle">' + (!obj_res[b]['closure'] ? obj_res[b]['cinfo'] : obj_res[b]['closure_txt']) + '</h4>' + "\n";
2319 rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-info-icon">' + "\n";
2320 rday_booking_html += ' <span class="label label-info">' + obj_res[b]['id'] + '</span> ' + "\n";
2321 if (obj_res[b]['type'] && obj_res[b]['type'] == 'overbooking') {
2322 rday_booking_html += ' <span class="label label-error vbo-label-overbooking">' + Joomla.JText._('VBO_BTYPE_OVERBOOKING') + '</span>' + "\n";
2323 }
2324 rday_booking_html += ' <span class="badge badge-' + badge_type + '">' + obj_res[b]['status_lbl'] + '</span>' + "\n";
2325 rday_booking_html += ' <span class="vbo-w-guestmessages-message-staydates">' + "\n";
2326 rday_booking_html += ' <span class="vbo-w-guestmessages-message-staydates-in">' + obj_res[b]['checkin_short'] + '</span>' + "\n";
2327 rday_booking_html += ' <span class="vbo-w-guestmessages-message-staydates-sep">-</span>' + "\n";
2328 rday_booking_html += ' <span class="vbo-w-guestmessages-message-staydates-out">' + obj_res[b]['checkout_short'] + '</span>' + "\n";
2329 rday_booking_html += ' </span>' + "\n";
2330 rday_booking_html += ' </div>' + "\n";
2331 rday_booking_html += ' </div>' + "\n";
2332 rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-info-date">' + "\n";
2333 rday_booking_html += ' <span>' + obj_res[b]['book_date'] + '</span>' + "\n";
2334 rday_booking_html += ' <span>' + obj_res[b]['book_time'] + '</span>' + "\n";
2335 if (obj_res[b].hasOwnProperty('meals_included')) {
2336 rday_booking_html += ' <div class="vbo-wider-badges-wrap">' + "\n";
2337 obj_res[b]['meals_included'].forEach((meal_enum) => {
2338 rday_booking_html += ' <div class="badge badge-info">' + meal_enum + '</div>' + "\n";
2339 });
2340 rday_booking_html += ' </div>' + "\n";
2341 }
2342 rday_booking_html += ' </div>' + "\n";
2343 rday_booking_html += ' </div>' + "\n";
2344 rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-info-msg">' + "\n";
2345 rday_booking_html += ' <div>' + ota_bid_info + nights_guests.join(', ') + '</div>' + "\n";
2346 if (obj_res[b].hasOwnProperty('sub_units_data')) {
2347 rday_booking_html += ' <div class="vbo-rdaybooking-subunits">' + "\n";
2348 for (let sub_rname in obj_res[b]['sub_units_data']) {
2349 if (!obj_res[b]['sub_units_data'].hasOwnProperty(sub_rname)) {
2350 continue;
2351 }
2352 rday_booking_html += '<span class="label label-success">' + (obj_res[b]['roomsnum'] > 1 ? sub_rname + ' ' : '') + '#' + obj_res[b]['sub_units_data'][sub_rname] + '</span>' + "\n";
2353 }
2354 rday_booking_html += ' </div>' + "\n";
2355 } else if (obj_res[b].hasOwnProperty('missing_index') && obj_res[b]['missing_index']) {
2356 rday_booking_html += ' <div class="vbo-rdaybooking-subunits">' + "\n";
2357 rday_booking_html += ' <span class="label label-warning">' + Joomla.JText._('VBO_MISSING_SUBUNIT') + '</span>' + "\n";
2358 if (obj_res[b].hasOwnProperty('av_room_indexes')) {
2359 // build drop downs to set the sub-unit to each room with no data
2360 rday_booking_html += ' <div class="vbo-rdaybooking-subunits-list">' + "\n";
2361 for (var av_rindex in obj_res[b]['av_room_indexes']) {
2362 if (!obj_res[b]['av_room_indexes'].hasOwnProperty(av_rindex) || !obj_res[b]['av_room_indexes'][av_rindex].hasOwnProperty('name') || !obj_res[b]['av_room_indexes'][av_rindex].hasOwnProperty('list')) {
2363 continue;
2364 }
2365 var set_subunit_node = '<select onchange="vboSetSubunit(\'' + obj_res[b]['id'] + '\', \'' + obj_res[b]['av_room_indexes'][av_rindex]['rid'] + '\', \'' + av_rindex + '\', this.value);">' + "\n";
2366 set_subunit_node += '<option value="">' + obj_res[b]['av_room_indexes'][av_rindex]['name'] + '</option>' + "\n";
2367 for (var rindex_k in obj_res[b]['av_room_indexes'][av_rindex]['list']) {
2368 if (!obj_res[b]['av_room_indexes'][av_rindex]['list'].hasOwnProperty(rindex_k)) {
2369 continue;
2370 }
2371 set_subunit_node += '<option value="' + rindex_k + '">' + obj_res[b]['av_room_indexes'][av_rindex]['list'][rindex_k] + '</option>' + "\n";
2372 }
2373 set_subunit_node += '</select>' + "\n";
2374 // append drop down
2375 rday_booking_html += set_subunit_node;
2376 }
2377 rday_booking_html += ' </div>' + "\n";
2378 }
2379 rday_booking_html += ' </div>' + "\n";
2380 }
2381 rday_booking_html += ' </div>' + "\n";
2382 rday_booking_html += '</div>' + "\n";
2383
2384 // set booking HTML to node
2385 rday_booking.append(rday_booking_html);
2386
2387 if (res_type_separator !== null) {
2388 // append the separator to toggle the cancelled reservations
2389 rday_bookings_list.append(res_type_separator);
2390 }
2391
2392 // append booking node to list
2393 rday_bookings_list.append(rday_booking);
2394 }
2395
2396 // finalize response nodes
2397 rday_bookings_wrap.append(rday_bookings_list);
2398
2399 // append the response
2400 rday_bookings_modal_body.append(rday_bookings_wrap);
2401 } catch(err) {
2402 console.error('could not parse JSON response', err, res);
2403 alert('Could not parse JSON response');
2404 }
2405 },
2406 (err) => {
2407 // stop loading and display alert message
2408 VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
2409 console.error(err);
2410 alert(err.responseText);
2411 }
2412 );
2413 }
2414
2415 if (jQuery(this).hasClass('busy') && !jQuery(this).hasClass('vbo-partially')) {
2416 // abort when fully booked date
2417 return;
2418 }
2419
2420 if (jQuery(this).hasClass('busytmplock') || jQuery(this).hasClass('subroom-busy')) {
2421 // abort when pending record or sub-room
2422 return;
2423 }
2424
2425 // update clicked dates info
2426 var curday = jQuery(this).attr('data-day');
2427 var roomid = jQuery(this).parent('tr').find('td.roomname').attr('data-roomid');
2428 roomid = roomid && roomid.length ? roomid : '';
2429 last_room_click = roomid;
2430 if (!last_date_click) {
2431 last_date_click = curday;
2432 } else if (last_date_click && next_date_click) {
2433 last_date_click = curday;
2434 next_date_click = '';
2435 } else {
2436 var from_info = new Date(last_date_click);
2437 var to_info = new Date(curday);
2438 if (from_info.getTime() < to_info.getTime()) {
2439 next_date_click = curday;
2440 } else {
2441 last_date_click = curday;
2442 next_date_click = '';
2443 }
2444 }
2445
2446 // button shake effect
2447 jQuery('.vbo-ovrv-flt-butn').addClass('vbo-ovrv-flt-butn-shake');
2448 setTimeout(function() {
2449 jQuery('.vbo-ovrv-flt-butn').removeClass('vbo-ovrv-flt-butn-shake');
2450 }, 1000);
2451 });
2452 /* Show New Reservation Form & Modal with bookings - End */
2453
2454 /* Hover Tooltip Start */
2455 jQuery('td.busy, td.busytmplock, td.subroom-busy').not('.vbo-widget-booskcal-cell-mday').hover(function() {
2456 registerHoveringTooltip(this);
2457 }, unregisterHoveringTooltip);
2458
2459 jQuery(document.body).on('click', '.vbo-overview-tip-bctag', function() {
2460 if (!jQuery(this).parent().find(".vbo-overview-tip-bctag-subtip").length) {
2461 jQuery(".vbo-overview-tip-bctag-subtip").remove();
2462 var cur_color = jQuery(this).attr("data-ctagcolor");
2463 var cur_bid = jQuery(this).attr("data-bid");
2464 jQuery(this).after("<div class=\"vbo-overview-tip-bctag-subtip\">"+bctags_tip+"</div>");
2465 jQuery(this).parent().find(".vbo-overview-tip-bctag-subtip").find(".vbo-overview-tip-bctag-subtip-circle[data-ctagcolor='"+cur_color+"']").addClass("vbo-overview-tip-bctag-activecircle").css('border-color', cur_color);
2466 jQuery(this).parent().find(".vbo-overview-tip-bctag-subtip").find(".vbo-overview-tip-bctag-subtip-circle").attr('data-bid', cur_bid);
2467 jQuery(".vbo-overview-tip-bctag-subtip .hasTooltip").tooltip();
2468 } else {
2469 jQuery(".vbo-overview-tip-bctag-subtip").remove();
2470 }
2471 });
2472
2473 var applying_tag = false;
2474
2475 jQuery(document.body).on('click', '.vbo-overview-tip-bctag-subtip-circle', function() {
2476 if (applying_tag === true) {
2477 return false;
2478 }
2479 applying_tag = true;
2480 var clickelem = jQuery(this);
2481 var ctagkey = clickelem.attr('data-ctagkey');
2482 var bid = clickelem.attr('data-bid');
2483 // set opacity to circles as loading
2484 jQuery('.vbo-overview-tip-bctag-subtip-circle').css('opacity', '0.6');
2485
2486 // perform the request
2487 VBOCore.doAjax(
2488 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=setbookingtag'); ?>",
2489 {
2490 tmpl: "component",
2491 idorder: bid,
2492 tagkey: ctagkey
2493 },
2494 (res) => {
2495 applying_tag = false;
2496 if (res.indexOf('e4j.error') >= 0 ) {
2497 // console.log(res);
2498 alert(res.replace("e4j.error.", ""));
2499 //restore loading opacity in circles
2500 jQuery('.vbo-overview-tip-bctag-subtip-circle').css('opacity', '1');
2501 } else {
2502 var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
2503 var last_bid = jQuery(clickelem).closest(".vbo-hascolortag").attr('data-lastbid');
2504 <?php if ($pbmode == 'tags') { echo 'jQuery(".vbo-hascolortag[data-lastbid=\'"+last_bid+"\']").each(function(k, v) { jQuery(this).css("background-color", obj_res.color).find("a").first().css("color", obj_res.fontcolor); });'; } ?>
2505 hideVboTooltip();
2506 }
2507 },
2508 (err) => {
2509 applying_tag = false;
2510 alert("Request Failed");
2511 // restore loading opacity in circles
2512 jQuery('.vbo-overview-tip-bctag-subtip-circle').css('opacity', '1');
2513 }
2514 );
2515 });
2516
2517 jQuery(document).keydown(function(e) {
2518 if ( e.keyCode == 27 ) {
2519 if (hovtip === true) {
2520 hideVboTooltip();
2521 }
2522 if (vbodialogorph_on === true) {
2523 hideVboDialogOverv(1);
2524 }
2525 }
2526 });
2527
2528 jQuery(document).mouseup(function(e) {
2529 if (!hovtip && !vbodialogorph_on) {
2530 return false;
2531 }
2532 if (hovtip) {
2533 var vbo_overlay_cont = jQuery(".vbo-overview-tipblock");
2534 if (!vbo_overlay_cont.is(e.target) && vbo_overlay_cont.has(e.target).length === 0) {
2535 hideVboTooltip();
2536 return true;
2537 }
2538 if (jQuery(".vbo-overview-tip-bctag-subtip").length) {
2539 var vbo_overlay_subtip_cont = jQuery(".vbo-overview-tip-bctag-wrap");
2540 if (!vbo_overlay_subtip_cont.is(e.target) && vbo_overlay_subtip_cont.has(e.target).length === 0) {
2541 jQuery(".vbo-overview-tip-bctag-subtip").remove();
2542 return true;
2543 }
2544 }
2545 }
2546 if (vbodialogorph_on) {
2547 var vbo_overlay_cont = jQuery(".vbo-info-overlay-content");
2548 if (!vbo_overlay_cont.is(e.target) && vbo_overlay_cont.has(e.target).length === 0) {
2549 hideVboDialogOverv(1);
2550 }
2551 }
2552 });
2553 /* Hover Tooltip End */
2554
2555 /* Toggle Sub-units */
2556 jQuery('.vbo-overview-subroom-toggle').click(function() {
2557 var roomid = jQuery(this).closest('td').attr('data-roomid');
2558 if (jQuery(this).hasClass('vbo-overview-subroom-toggle-active')) {
2559 jQuery("td.roomname[data-roomid='" + roomid + "']").find('span.vbo-overview-subroom-toggle').removeClass('vbo-overview-subroom-toggle-active').find('i').removeClass('fa-chevron-up').addClass('fa-chevron-down');
2560 // do not use .hide() or "display: none" may not work due to forced "display: table-row"
2561 jQuery("td.subroomname[data-roomid='-" + roomid + "']").parent('tr').css('display', 'none');
2562 // update storage object
2563 var storage_subunits_status = VBOCore.storageGetItem('vbo_overv_subunits_status');
2564 try {
2565 storage_subunits_status = JSON.parse(storage_subunits_status);
2566 storage_subunits_status = storage_subunits_status || {};
2567 } catch(e) {
2568 storage_subunits_status = {};
2569 }
2570 storage_subunits_status[roomid] = 0;
2571 VBOCore.storageSetItem('vbo_overv_subunits_status', storage_subunits_status);
2572 } else {
2573 jQuery("td.roomname[data-roomid='" + roomid + "']").find('span.vbo-overview-subroom-toggle').addClass('vbo-overview-subroom-toggle-active').find('i').removeClass('fa-chevron-down').addClass('fa-chevron-up');
2574 // do not use .show() or "display: block" may be added rather than "display: table-row"
2575 jQuery("td.subroomname[data-roomid='-" + roomid + "']").parent('tr').css('display', 'table-row');
2576 // update storage object
2577 var storage_subunits_status = VBOCore.storageGetItem('vbo_overv_subunits_status');
2578 try {
2579 storage_subunits_status = JSON.parse(storage_subunits_status);
2580 storage_subunits_status = storage_subunits_status || {};
2581 } catch(e) {
2582 storage_subunits_status = {};
2583 }
2584 storage_subunits_status[roomid] = 1;
2585 VBOCore.storageSetItem('vbo_overv_subunits_status', storage_subunits_status);
2586 }
2587 });
2588
2589 /* Display sub-units based on storage status */
2590 var storage_subunits_defstatus = VBOCore.storageGetItem('vbo_overv_subunits_status');
2591 try {
2592 storage_subunits_defstatus = JSON.parse(storage_subunits_defstatus);
2593 storage_subunits_defstatus = storage_subunits_defstatus || {};
2594 for (let rid in storage_subunits_defstatus) {
2595 if (!storage_subunits_defstatus.hasOwnProperty(rid)) {
2596 continue;
2597 }
2598 if (storage_subunits_defstatus[rid]) {
2599 // trigger click event to display the sub-units
2600 jQuery('td.roomname[data-roomid="' + rid + '"]').find('.vbo-overview-subroom-toggle').trigger('click');
2601 }
2602 }
2603 } catch(e) {
2604 // do nothing
2605 }
2606
2607 /* Default state for sticky table heads */
2608 vboToggleStickyTableHeaders(<?php echo (count($rows) * $mnum) > 10 && $cookie_sticky_heads != 'off' ? 1 : 0; ?>);
2609
2610 // check orphans (only if not disabled with the cookie)
2611 var hideorphans = false;
2612 var buiscuits = document.cookie;
2613 if (buiscuits.length) {
2614 var hideorphansck = "vboHideOrphans=1";
2615 if (buiscuits.indexOf(hideorphansck) >= 0) {
2616 hideorphans = true;
2617 }
2618 }
2619 if (!hideorphans && <?php echo $vbo_auth_pricing ? 'true' : 'false'; ?>) {
2620 // make the request
2621
2622 VBOCore.doAjax(
2623 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=orphanscount'); ?>",
2624 {
2625 from: '<?php echo date($df, $tsstart); ?>',
2626 months: <?php echo $mnum; ?>,
2627 tmpl: "component"
2628 },
2629 (res) => {
2630 var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
2631 var orphans_list = '';
2632 for (var rid in obj_res) {
2633 if (!obj_res.hasOwnProperty(rid)) {
2634 continue;
2635 }
2636 orphans_list += '<div class="vbo-orphans-info-room">';
2637 orphans_list += ' <h4 class="vbo-orphans-roomname">'+obj_res[rid]['name']+'</h4>';
2638 orphans_list += ' <div class="vbo-orphans-info-dates">';
2639 for (var dind in obj_res[rid]['rdates']) {
2640 if (!obj_res[rid]['rdates'].hasOwnProperty(dind)) {
2641 continue;
2642 }
2643 orphans_list += ' <div class="vbo-orphans-info-date">'+obj_res[rid]['rdates'][dind]+'</div>';
2644 }
2645 orphans_list += ' </div>';
2646 orphans_list += ' <div class="vbo-orphans-info-btn">';
2647 orphans_list += ' <a href="index.php?option=com_vikbooking&task=ratesoverv&cid[]='+rid+'&startdate='+obj_res[rid]['linkd']+'" class="btn btn-primary" target="_blank"><?php echo addslashes(JText::translate('VBORPHANSCHECKBTN')); ?></a>';
2648 orphans_list += ' </div>';
2649 orphans_list += '</div>';
2650 }
2651 if (orphans_list.length) {
2652 // show the modal
2653 jQuery('.vbo-orphans-info-list').html(orphans_list);
2654 jQuery('.vbo-orphans-overlay-block').fadeIn();
2655 vbodialogorph_on = true;
2656 }
2657 },
2658 (err) => {
2659 console.log("orphanscount Request Failed");
2660 }
2661 );
2662 }
2663 //
2664
2665 // fests
2666 jQuery(document.body).on("click", "th.bluedays", function() {
2667 if (jQuery(this).hasClass('skip-bluedays-click')) {
2668 return;
2669 }
2670 var ymd = jQuery(this).attr('data-ymd');
2671 var daytitle = jQuery(this).attr('data-readymd');
2672 if (jQuery(this).hasClass('vbo-overv-festcell')) {
2673 // cell has fests
2674 if (!vboFests.hasOwnProperty(ymd)) {
2675 return;
2676 }
2677 vboRenderFests(ymd, daytitle);
2678 } else {
2679 // let the admin create a new fest
2680
2681 // update ymd key for the selected date, useful for adding new fests
2682 jQuery('.vbo-overlay-fests-addnew').attr('data-ymd', ymd);
2683
2684 // unset content and display modal for just adding a new fest
2685 jQuery('.vbo-overlay-fests-list').html('');
2686 var fests_modal = VBOCore.displayModal({
2687 suffix: 'overv-mng-fests',
2688 extra_class: 'vbo-modal-rounded vbo-modal-tall',
2689 title: '<?php VikBookingIcons::e('star'); ?> <span>' + daytitle + '</span>',
2690 footer_right: '<button type="button" class="btn btn-success" onclick="vboAddFest();">' + Joomla.JText._('VBSAVE') + '</button>',
2691 dismiss_event: 'vbo-dismiss-modal-overv-mng-fests',
2692 onDismiss: () => {
2693 jQuery('.vbo-overv-mngfest-wrap').appendTo('.vbo-overv-mngfest-block');
2694 },
2695 });
2696
2697 // set modal body
2698 jQuery('.vbo-overv-mngfest-wrap').appendTo(fests_modal);
2699 }
2700 });
2701 //
2702
2703 // room-day notes
2704 jQuery(document.body).on("click", ".vbo-roomdaynote-display", function() {
2705 if (!jQuery(this).closest('.vbo-roomdaynote-trigger').length) {
2706 return;
2707 }
2708 var daytitle = new Array;
2709 var roomday_info = jQuery(this).closest('.vbo-roomdaynote-trigger').attr('data-roomday').split('_');
2710 // readable day
2711 var readymd = roomday_info[0];
2712 if (jQuery('.bluedays[data-ymd="' + roomday_info[0] + '"]').length) {
2713 readymd = jQuery('.bluedays[data-ymd="' + roomday_info[0] + '"]').attr('data-readymd');
2714 }
2715 daytitle.push(readymd);
2716 // room name
2717 if (jQuery('.roomname[data-roomid="' + roomday_info[1] + '"]').length) {
2718 daytitle.push(jQuery('.roomname[data-roomid="' + roomday_info[1] + '"]').first().find('.vbo-overview-roomname').text());
2719 }
2720 // sub-unit
2721 if (parseInt(roomday_info[2]) > 0 && jQuery('.subroomname[data-roomid="-' + roomday_info[1] + '"]').length) {
2722 daytitle.push(jQuery('.subroomname[data-roomid="-' + roomday_info[1] + '"]').find('.vbo-overview-subroomname').eq((parseInt(roomday_info[2]) - 1)).text());
2723 }
2724
2725 // display modal
2726 var rdaynotes_modal = VBOCore.displayModal({
2727 suffix: 'overv-mng-rdaynotes',
2728 extra_class: 'vbo-modal-rounded vbo-modal-tall',
2729 title: '<?php VikBookingIcons::e('comment'); ?> <span>' + daytitle.join(', ') + '</span>',
2730 footer_right: '<button type="button" class="btn btn-success" onclick="vboAddRoomDayNote();">' + Joomla.JText._('VBSAVE') + '</button>',
2731 dismiss_event: 'vbo-dismiss-modal-overv-mng-rdaynotes',
2732 onDismiss: () => {
2733 jQuery('.vbo-overv-mngroomdaynotes-wrap').appendTo('.vbo-overv-mngroomdaynotes-block');
2734 },
2735 });
2736
2737 // set modal body
2738 jQuery('.vbo-overv-mngroomdaynotes-wrap').appendTo(rdaynotes_modal);
2739
2740 // populate current room day notes
2741 vboRenderRdayNotes(roomday_info[0], roomday_info[1], roomday_info[2], readymd);
2742 });
2743 //
2744
2745 <?php
2746 /**
2747 * Handle fixed table heads when on snake-layout (scroll), which is completely
2748 * different than the table-layout sticky table headers (handled through jQuery).
2749 *
2750 * @since 1.16.9 (J) - 1.6.9 (WP)
2751 */
2752 if ($cookie_sticky_heads == 'off') {
2753 ?>
2754 // listen to the document scroll event to handle the fixed table heads
2755 document.addEventListener('scroll', vboOvervHandleVScrollTableHeads);
2756
2757 // scan all tables for listening to their horizontal scrolling event
2758 let tables = document.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
2759 tables.forEach((table) => {
2760 table.parentNode.addEventListener('scroll', vboOvervHandleHScrollTableHead);
2761 });
2762 <?php
2763 }
2764 ?>
2765 });
2766
2767 /**
2768 * Document vertical scroll event handler for the fixed table heads.
2769 *
2770 * @param object e the document scroll event object.
2771 *
2772 * @return void
2773 *
2774 * @since 1.16.9 (J) - 1.6.9 (WP)
2775 */
2776 function vboOvervHandleVScrollTableHeads(e) {
2777 // register throttling callback
2778 VBOCore.throttleTimer(() => {
2779 // get all eligible tables
2780 let tables = document.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
2781 if (!tables.length) {
2782 // un-register event
2783 document.removeEventListener('scroll', vboOvervHandleVScrollTableHeads);
2784 return;
2785 }
2786
2787 // pool of fixed table heads
2788 let fixedTableHeadNodes = vboOvervBuildFixedTableHeads(tables);
2789
2790 if (fixedTableHeadNodes.length) {
2791 // append to body just the last table head node for the bottom farthest table
2792 document.body.appendChild(fixedTableHeadNodes[fixedTableHeadNodes.length - 1]);
2793 }
2794 }, 400);
2795 }
2796
2797 /**
2798 * Table-element horizontal scroll event handler for updating the fixed table heads.
2799 *
2800 * @param object e the table-element scroll event object.
2801 *
2802 * @return void
2803 *
2804 * @since 1.16.9 (J) - 1.6.9 (WP)
2805 */
2806 function vboOvervHandleHScrollTableHead(e) {
2807 // register throttling callback
2808 VBOCore.throttleTimer(() => {
2809 // horizontal scrolling happens on the parent DIV node
2810 let tables = e.target.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
2811 if (tables.length !== 1) {
2812 // un-register event for missing table
2813 e.target.removeEventListener('scroll', vboOvervHandleHScrollTableHead);
2814 return;
2815 }
2816
2817 // pool of fixed table heads
2818 let fixedTableHeadNodes = vboOvervBuildFixedTableHeads(tables);
2819
2820 if (fixedTableHeadNodes.length) {
2821 // append to body the first and only table head node expected
2822 document.body.appendChild(fixedTableHeadNodes[0]);
2823 }
2824 }, 200);
2825 }
2826
2827 /**
2828 * Builds the list of element nodes for the fixed table heads.
2829 *
2830 * @param object tables The table elements to parse.
2831 *
2832 * @return object[]
2833 *
2834 * @since 1.16.9 (J) - 1.6.9 (WP)
2835 */
2836 function vboOvervBuildFixedTableHeads(tables) {
2837 // pool of fixed table heads
2838 let fixedTableHeadNodes = [];
2839
2840 // get document body scroll top value
2841 let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
2842
2843 // platform fixed offset top
2844 let platformAdminBar = <?php echo VBOPlatformDetection::isWordPress() ? 'document.querySelector("#wpadminbar")' : '(document.querySelector(".navbar") || document.querySelector("#subhead-container"))'; ?>;
2845 let fixedOffsetTop = platformAdminBar ? platformAdminBar.offsetHeight : 0;
2846 if (fixedOffsetTop) {
2847 // ensure the responsive mode has got a fixed/absolute positioning
2848 let adminBarPosition = platformAdminBar.style.position || '';
2849 if (adminBarPosition != 'fixed' && adminBarPosition != 'absolute') {
2850 // no fixed admin bar
2851 fixedOffsetTop = 0;
2852 }
2853 }
2854
2855 // scan all table elements
2856 (tables || []).forEach((table, index) => {
2857 // always check for existing fixed table head element
2858 let scrollingHeadElement = document.querySelector('.vbo-overv-sticky-scroll-month[data-table-index="' + index + '"]');
2859 if (scrollingHeadElement) {
2860 // remove fixed table head element from DOM, will be reconstructed if needed
2861 scrollingHeadElement.remove();
2862 }
2863
2864 // get table head element node
2865 let tableHeadNode = table.querySelector('tr.vboverviewtablerowone');
2866
2867 // get position of current table head
2868 let tableHeadHeight = tableHeadNode.offsetHeight;
2869
2870 // make sure the table head is NOT visible, hence requires a fixed head
2871 if (scrollTop > (table.offsetTop + tableHeadHeight - (fixedOffsetTop / 2))) {
2872 if (scrollTop > (table.offsetTop + table.offsetHeight)) {
2873 // scrolling went even below the table
2874 return;
2875 }
2876
2877 // build main element node
2878 let fixedTableHead = document.createElement('div');
2879 fixedTableHead.className = 'vbo-overv-sticky-scroll-month';
2880 fixedTableHead.style.top = fixedOffsetTop + 'px';
2881 fixedTableHead.style.left = table.parentNode.getBoundingClientRect().x + 'px';
2882 fixedTableHead.setAttribute('data-table-index', index);
2883
2884 // inner element node
2885 let fixedTableHeadWrap = document.createElement('div');
2886 fixedTableHeadWrap.className = 'vbo-overv-sticky-scroll-month-wrap';
2887
2888 // month-name element node
2889 let tableHeadMonth = tableHeadNode.querySelector('.vbo-overview-month');
2890 let fixedTableHeadMonth = document.createElement('div');
2891 fixedTableHeadMonth.className = 'vbo-overv-sticky-scroll-month-m';
2892 fixedTableHeadMonth.style.width = tableHeadMonth.offsetWidth + 'px';
2893 fixedTableHeadMonth.appendChild(
2894 document.createTextNode(tableHeadMonth.innerText)
2895 );
2896 fixedTableHeadWrap.appendChild(fixedTableHeadMonth);
2897
2898 // obtain the list of visible month-day cells
2899 vboOvervVisibleTableHeadNodes(table, index).forEach((mday) => {
2900 // month-day cell element node
2901 let fixedTableHeadMday = document.createElement('div');
2902 fixedTableHeadMday.className = 'vbo-overv-sticky-scroll-month-d';
2903 fixedTableHeadMday.style.width = mday['vwidth'] + 'px';
2904 fixedTableHeadMday.addEventListener('click', () => {
2905 try {
2906 // use jQuery to trigger the click event, or plain JS won't work
2907 /*
2908 document
2909 .querySelector('table.vboverviewtable.vbo-overv-sticky-table-head-off[data-table-index="' + index + '"]')
2910 .querySelector('.bluedays[data-ymd="' + mday['ymd'] + '"]')
2911 .dispatchEvent(new Event('click'));
2912 */
2913 jQuery('table.vboverviewtable.vbo-overv-sticky-table-head-off[data-table-index="' + index + '"]')
2914 .find('.bluedays[data-ymd="' + mday['ymd'] + '"]')
2915 .trigger('click');
2916 } catch(e) {
2917 // do nothing
2918 }
2919 });
2920
2921 // week-day element
2922 let wdayEl = document.createElement('span');
2923 wdayEl.className = 'vbo-overv-sticky-scroll-month-day';
2924 wdayEl.appendChild(
2925 document.createTextNode(mday['wday'])
2926 );
2927 fixedTableHeadMday.appendChild(wdayEl);
2928
2929 // month-day element
2930 let mdayEl = document.createElement('span');
2931 mdayEl.className = 'vbo-overv-sticky-scroll-month-day-num';
2932 mdayEl.appendChild(
2933 document.createTextNode(mday['mday'])
2934 );
2935 fixedTableHeadMday.appendChild(mdayEl);
2936
2937 // append month-day cell element node
2938 fixedTableHeadWrap.appendChild(fixedTableHeadMday);
2939 });
2940
2941 // append wrapper
2942 fixedTableHead.appendChild(fixedTableHeadWrap);
2943
2944 // push the final element node to the pool
2945 fixedTableHeadNodes.push(fixedTableHead);
2946 }
2947 });
2948
2949 return fixedTableHeadNodes;
2950 }
2951
2952 /**
2953 * Builds a list of visible month-day cells in a table head.
2954 *
2955 * @param object table the NodeList element representing a table.
2956 * @param number index the NodeList element index we are parsing.
2957 *
2958 * @return object[]
2959 *
2960 * @since 1.16.9 (J) - 1.6.9 (WP)
2961 */
2962 function vboOvervVisibleTableHeadNodes(table, index) {
2963 // get the main table head element
2964 let tableHeadElement = table.querySelector('tr.vboverviewtablerowone');
2965
2966 // get month element of the given table
2967 let monthElement = tableHeadElement.querySelector('.vbo-overview-month');
2968 let monthName = monthElement.innerText;
2969 let monthLeft = monthElement.offsetLeft - 2;
2970 let monthWidth = monthElement.offsetWidth;
2971
2972 // get horizontal scroll left and table width from parent node
2973 let tableScrollLeft = table.parentNode.scrollLeft;
2974 let tableViewWidth = table.parentNode.offsetWidth;
2975
2976 // pool of visible month-day cells
2977 let visibleMonthDays = [];
2978
2979 // scan all visible month-day elements
2980 tableHeadElement.querySelectorAll('.bluedays:not(.vbo-overview-month)').forEach((cell) => {
2981 let monthDayLeft = cell.offsetLeft;
2982 let monthDayWidth = cell.offsetWidth;
2983
2984 // ensure cell is visible from left scroll
2985 if ((monthDayLeft + monthDayWidth) >= (monthLeft + monthWidth)) {
2986 // ensure cell is also visible from right scroll
2987 if ((monthLeft + tableViewWidth) > monthDayLeft) {
2988 // calculate cell exact visible width
2989 let cellViewWidth = monthDayWidth;
2990 if ((monthDayLeft + monthDayWidth) > (monthLeft + tableViewWidth)) {
2991 // cell is cut from right scroll, count visible width
2992 cellViewWidth = monthDayWidth - ((monthDayLeft + monthDayWidth) - (tableViewWidth + monthLeft));
2993 } else if ((monthWidth + tableScrollLeft) > monthDayLeft) {
2994 // cell is cut from left scroll, count visible width
2995 cellViewWidth = monthDayWidth - ((monthWidth + tableScrollLeft) - monthDayLeft);
2996 }
2997 cellViewWidth = cellViewWidth > monthDayWidth || cellViewWidth < 1 ? 1 : cellViewWidth;
2998
2999 // push cell details
3000 visibleMonthDays.push({
3001 wday: cell.querySelector('.vbo-overw-tablewday').innerText,
3002 mday: cell.querySelector('.vbo-overw-tablemday').innerText,
3003 ymd: cell.getAttribute('data-ymd'),
3004 vwidth: cellViewWidth,
3005 });
3006 }
3007 }
3008 });
3009
3010 return visibleMonthDays;
3011 }
3012
3013 /**
3014 * Fests
3015 */
3016 function vboRenderFests(day, daytitle) {
3017 // compose fests information
3018 var fests_html = '';
3019 if (vboFests[day] && vboFests[day]['festinfo'] && vboFests[day]['festinfo'].length) {
3020 for (var i = 0; i < vboFests[day]['festinfo'].length; i++) {
3021 var fest = vboFests[day]['festinfo'][i];
3022 fests_html += '<div class="vbo-overlay-fest-details">';
3023 fests_html += ' <div class="vbo-fest-info">';
3024 fests_html += ' <div class="vbo-fest-name">' + fest['trans_name'] + '</div>';
3025 fests_html += ' <div class="vbo-fest-desc">' + fest['descr'].replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br />' + '$2') + '</div>';
3026 fests_html += ' </div>';
3027 fests_html += ' <div class="vbo-fest-cmds">';
3028 fests_html += ' <button type="button" class="btn btn-danger" onclick="vboRemoveFest(\'' + day + '\', \'' + i + '\', \'' + fest['type'] + '\', this);"><?php VikBookingIcons::e('trash-alt', 'no-margin'); ?></button>';
3029 fests_html += ' </div>';
3030 fests_html += '</div>';
3031 }
3032 }
3033
3034 // update ymd key for the selected date, useful for adding new fests
3035 jQuery('.vbo-overlay-fests-addnew').attr('data-ymd', day);
3036
3037 // set content and display modal
3038 jQuery('.vbo-overlay-fests-list').html(fests_html);
3039
3040 if (typeof daytitle !== 'undefined') {
3041 // display modal only when we are not deleting a fest
3042 var fests_modal = VBOCore.displayModal({
3043 suffix: 'overv-mng-fests',
3044 extra_class: 'vbo-modal-rounded vbo-modal-tall',
3045 title: '<?php VikBookingIcons::e('star'); ?> <span>' + daytitle + '</span>',
3046 footer_right: '<button type="button" class="btn btn-success" onclick="vboAddFest();">' + Joomla.JText._('VBSAVE') + '</button>',
3047 dismiss_event: 'vbo-dismiss-modal-overv-mng-fests',
3048 onDismiss: () => {
3049 jQuery('.vbo-overv-mngfest-wrap').appendTo('.vbo-overv-mngfest-block');
3050 },
3051 });
3052
3053 // set modal body
3054 jQuery('.vbo-overv-mngfest-wrap').appendTo(fests_modal);
3055 }
3056 }
3057
3058 function vboRemoveFest(day, index, fest_type, that) {
3059 if (!confirm('<?php echo addslashes(JText::translate('VBDELCONFIRM')); ?>')) {
3060 return false;
3061 }
3062 var elem = jQuery(that);
3063 // make the AJAX request to the controller to remove this fest from the DB
3064 VBOCore.doAjax(
3065 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=remove_fest'); ?>",
3066 {
3067 tmpl: "component",
3068 dt: day,
3069 ind: index,
3070 type: fest_type
3071 },
3072 (res) => {
3073 if (res.indexOf('e4j.ok') >= 0) {
3074 // delete fest also from the json-decode array of objects
3075 if (vboFests[day] && vboFests[day]['festinfo']) {
3076 // use splice to remove the desired index from array, or delete would not make the length of the array change
3077 vboFests[day]['festinfo'].splice(index, 1);
3078 // re-build indexes of delete buttons, fundamental for removing the right index at next click
3079 vboRenderFests(day);
3080 if (!vboFests[day]['festinfo'].length) {
3081 // delete also this date object from fests
3082 delete vboFests[day];
3083 // no more fests, remove the class for this date from all cells
3084 jQuery('th.bluedays[data-ymd="'+day+'"]').removeClass('vbo-overv-festcell');
3085 jQuery('td.notbusy[data-day="'+day+'"]').removeClass('vbo-overv-festcell');
3086 jQuery('td.subnotbusy[data-day="'+day+'"]').removeClass('vbo-overv-festcell');
3087 jQuery('td.busy[data-day="'+day+'"]').removeClass('vbo-overv-festcell');
3088 jQuery('td.subroom-busy[data-day="'+day+'"]').removeClass('vbo-overv-festcell');
3089 }
3090 }
3091 elem.closest('.vbo-overlay-fest-details').remove();
3092 } else {
3093 // console.log(res);
3094 alert('Invalid response');
3095 }
3096 },
3097 (err) => {
3098 alert('Request failed');
3099 }
3100 );
3101 }
3102
3103 function vboAddFest() {
3104 var ymd = jQuery('.vbo-overlay-fests-addnew').attr('data-ymd');
3105 var fest_name = jQuery('#vbo-newfest-name').val();
3106 var fest_descr = jQuery('#vbo-newfest-descr').val();
3107 if (!fest_name || !fest_name.length) {
3108 return false;
3109 }
3110 // make the AJAX request to the controller to add this fest to the DB
3111 VBOCore.doAjax(
3112 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=add_fest'); ?>",
3113 {
3114 tmpl: "component",
3115 dt: ymd,
3116 type: "custom",
3117 name: fest_name,
3118 descr: fest_descr
3119 },
3120 (res) => {
3121 // parse the JSON response that contains the fest object for the passed date
3122 try {
3123 var stored_fest = typeof res === 'string' ? JSON.parse(res) : res;
3124 if (!vboFests.hasOwnProperty(stored_fest['dt'])) {
3125 // we need to add the proper class to all cells to show that there is a fest
3126 jQuery('th.bluedays[data-ymd="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
3127 jQuery('td.notbusy[data-day="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
3128 jQuery('td.subnotbusy[data-day="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
3129 jQuery('td.busy[data-day="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
3130 jQuery('td.subroom-busy[data-day="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
3131 }
3132 vboFests[stored_fest['dt']] = stored_fest;
3133 // hide modal
3134 VBOCore.emitEvent('vbo-dismiss-modal-overv-mng-fests');
3135 // reset input fields
3136 jQuery('#vbo-newfest-name').val('');
3137 jQuery('#vbo-newfest-descr').val('');
3138 } catch (e) {
3139 // console.log(res);
3140 alert('Invalid response');
3141 return false;
3142 }
3143 },
3144 (err) => {
3145 alert('Request failed');
3146 }
3147 );
3148 }
3149
3150 /**
3151 * Room-day notes
3152 */
3153 var rdaynote_icn_full = '<?php echo VikBookingIcons::i('sticky-note', 'vbo-roomdaynote-display'); ?>';
3154 var rdaynote_icn_empty = '<?php echo VikBookingIcons::i('far fa-sticky-note', 'vbo-roomdaynote-display'); ?>';
3155 function vboRenderRdayNotes(day, idroom, subunit, readymd) {
3156 // compose fests information
3157 var notes_html = '';
3158 var keyid = day + '_' + idroom + '_' + subunit;
3159 if (vboRdayNotes.hasOwnProperty(keyid) && vboRdayNotes[keyid]['info'] && vboRdayNotes[keyid]['info'].length) {
3160 for (var i = 0; i < vboRdayNotes[keyid]['info'].length; i++) {
3161 var note_data = vboRdayNotes[keyid]['info'][i];
3162 notes_html += '<div class="vbo-overlay-fest-details vbo-modal-roomdaynotes-note-details">';
3163 notes_html += ' <div class="vbo-fest-info vbo-modal-roomdaynotes-note-info">';
3164 notes_html += ' <div class="vbo-fest-name vbo-modal-roomdaynotes-note-name">' + note_data['name'] + '</div>';
3165 notes_html += ' <div class="vbo-fest-desc vbo-modal-roomdaynotes-note-desc">' + note_data['descr'].replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br />' + '$2') + '</div>';
3166 notes_html += ' </div>';
3167 notes_html += ' <div class="vbo-fest-cmds vbo-modal-roomdaynotes-note-cmds">';
3168 notes_html += ' <button type="button" class="btn btn-danger" onclick="vboRemoveRdayNote(\'' + i + '\', \'' + day + '\', \'' + idroom + '\', \'' + subunit + '\', \'' + note_data['type'] + '\', this);"><?php VikBookingIcons::e('trash-alt', 'no-margin'); ?></button>';
3169 notes_html += ' </div>';
3170 notes_html += '</div>';
3171 }
3172 }
3173 // update attributes keys for the selected date, useful for adding new notes
3174 jQuery('.vbo-modal-roomdaynotes-addnew').attr('data-ymd', day).attr('data-roomid', idroom).attr('data-subroomid', subunit);
3175 if (readymd !== null) {
3176 jQuery('.vbo-modal-roomdaynotes-addnew').attr('data-readymd', readymd);
3177 jQuery('.vbo-newrdnote-dayto-val').text(readymd);
3178 }
3179 // set content and display modal
3180 jQuery('.vbo-modal-roomdaynotes-list').html(notes_html);
3181 }
3182
3183 function vboAddRoomDayNote(that) {
3184 var mainelem = jQuery('.vbo-modal-roomdaynotes-addnew');
3185 var ymd = mainelem.attr('data-ymd');
3186 var roomid = mainelem.attr('data-roomid');
3187 var subroomid = mainelem.attr('data-subroomid');
3188 var note_name = jQuery('#vbo-newrdnote-name').val();
3189 var note_descr = jQuery('#vbo-newrdnote-descr').val();
3190 var note_cdays = jQuery('#vbo-newrdnote-cdays').val();
3191 if (!note_name.length && !note_descr.length) {
3192 alert('Missing required fields');
3193 return false;
3194 }
3195 // make the AJAX request to the controller to add this note to the DB
3196 VBOCore.doAjax(
3197 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=add_roomdaynote'); ?>",
3198 {
3199 tmpl: "component",
3200 dt: ymd,
3201 idroom: roomid,
3202 subunit: subroomid,
3203 type: "custom",
3204 name: note_name,
3205 descr: note_descr,
3206 cdays: note_cdays
3207 },
3208 (res) => {
3209 // parse the JSON response that contains the note object for the passed date
3210 try {
3211 var stored_notes = typeof res === 'string' ? JSON.parse(res) : res;
3212 for (var keyid in stored_notes) {
3213 if (!stored_notes.hasOwnProperty(keyid)) {
3214 continue;
3215 }
3216 if (!vboRdayNotes.hasOwnProperty(keyid) && jQuery('.vbo-roomdaynote-trigger[data-roomday="' + keyid + '"]').length) {
3217 // we need to add the proper class to the cell for this note (if it's visible)
3218 jQuery('.vbo-roomdaynote-trigger[data-roomday="' + keyid + '"]').parent('td').removeClass('vbo-roomdaynote-empty').addClass('vbo-roomdaynote-full').find('i').attr('class', rdaynote_icn_full);
3219 }
3220 // update global object with the new notes in any case
3221 vboRdayNotes[keyid] = stored_notes[keyid];
3222 }
3223 // hide modal
3224 VBOCore.emitEvent('vbo-dismiss-modal-overv-mng-rdaynotes');
3225 // reset input fields
3226 jQuery('#vbo-newrdnote-name').val('');
3227 jQuery('#vbo-newrdnote-descr').val('');
3228 jQuery('#vbo-newrdnote-cdays').val('0').trigger('change');
3229 } catch (e) {
3230 // console.log(res);
3231 alert('Invalid response');
3232 return false;
3233 }
3234 },
3235 (err) => {
3236 alert('Request failed');
3237 }
3238 );
3239 }
3240
3241 function vboRemoveRdayNote(index, day, idroom, subunit, note_type, that) {
3242 if (!confirm('<?php echo addslashes(JText::translate('VBDELCONFIRM')); ?>')) {
3243 return false;
3244 }
3245 var elem = jQuery(that);
3246 // make the AJAX request to the controller to remove this note from the DB
3247 VBOCore.doAjax(
3248 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=remove_roomdaynote'); ?>",
3249 {
3250 tmpl: "component",
3251 dt: day,
3252 idroom: idroom,
3253 subunit: subunit,
3254 ind: index,
3255 type: note_type
3256 },
3257 (res) => {
3258 if (res.indexOf('e4j.ok') >= 0) {
3259 var keyid = day + '_' + idroom + '_' + subunit;
3260 // delete note also from the json-decode array of objects
3261 if (vboRdayNotes[keyid] && vboRdayNotes[keyid]['info']) {
3262 // use splice to remove the desired index from array, or delete would not make the length of the array change
3263 vboRdayNotes[keyid]['info'].splice(index, 1);
3264 // re-build indexes of delete buttons, fundamental for removing the right index at next click
3265 vboRenderRdayNotes(day, idroom, subunit, null);
3266 if (!vboRdayNotes[keyid]['info'].length) {
3267 // delete also this date object from notes
3268 delete vboRdayNotes[keyid];
3269 // no more notes, update the proper class attribute for this cell (should be visible)
3270 if (jQuery('.vbo-roomdaynote-trigger[data-roomday="' + keyid + '"]').length) {
3271 jQuery('.vbo-roomdaynote-trigger[data-roomday="' + keyid + '"]').parent('td').removeClass('vbo-roomdaynote-full').addClass('vbo-roomdaynote-empty').find('i').attr('class', rdaynote_icn_empty);
3272 }
3273 }
3274 }
3275 elem.closest('.vbo-modal-roomdaynotes-note-details').remove();
3276 } else {
3277 // console.log(res);
3278 alert('Invalid response');
3279 }
3280 },
3281 (err) => {
3282 alert('Request failed');
3283 }
3284 );
3285 }
3286
3287 function vboRdayNoteCdaysCount() {
3288 var cdays = parseInt(jQuery('#vbo-newrdnote-cdays').val());
3289 var defymd = jQuery('.vbo-modal-roomdaynotes-addnew').attr('data-ymd');
3290 var defreadymd = jQuery('.vbo-modal-roomdaynotes-addnew').attr('data-readymd');
3291 defreadymd = !defreadymd || !defreadymd.length ? defymd : defreadymd;
3292 if (isNaN(cdays) || cdays < 1) {
3293 jQuery('.vbo-newrdnote-dayto-val').text(defreadymd);
3294 return;
3295 }
3296 // calculate target (until) date
3297 var targetdate = new Date(defymd);
3298 targetdate.setDate(targetdate.getDate() + cdays);
3299 var target_y = targetdate.getFullYear();
3300 var target_m = targetdate.getMonth() + 1;
3301 target_m = target_m < 10 ? '0' + target_m : target_m;
3302 var target_d = targetdate.getDate();
3303 target_d = target_d < 10 ? '0' + target_d : target_d;
3304 // display target date
3305 var display_target = target_y + '-' + target_m + '-' + target_d;
3306 // check if we can get the "read ymd property"
3307 if (jQuery('.bluedays[data-ymd="' + display_target + '"]').length) {
3308 display_target = jQuery('.bluedays[data-ymd="' + display_target + '"]').attr('data-readymd');
3309 }
3310 jQuery('.vbo-newrdnote-dayto-val').text(display_target);
3311 }
3312 </script>
3313
3314 <div class="vbo-orphans-overlay-block">
3315 <a class="vbo-info-overlay-close" href="javascript: void(0);"></a>
3316 <div class="vbo-info-overlay-content vbo-info-overlay-content-orphans">
3317 <h3><?php echo $vbo_app->createPopover(array('title' => JText::translate('VBORPHANSFOUND'), 'content' => JText::translate('VBORPHANSFOUNDSHELP'), 'icon_class' => VikBookingIcons::i('exclamation-triangle'))); ?> <?php echo JText::translate('VBORPHANSFOUND'); ?></h3>
3318 <div class="vbo-info-overlay-scroll-content">
3319 <div class="vbo-orphans-info-list"></div>
3320 </div>
3321 <div class="vbo-orphans-info-cmds">
3322 <div class="vbo-orphans-info-cmd">
3323 <button type="button" class="btn btn-success" onclick="javascript: hideVboDialogOverv(1);"><?php echo JText::translate('VBOBTNKEEPREMIND'); ?></button>
3324 </div>
3325 <div class="vbo-orphans-info-cmd">
3326 <button type="button" class="btn btn-danger" onclick="javascript: hideVboDialogOverv(-1);"><?php echo JText::translate('VBOBTNDONTREMIND'); ?></button>
3327 </div>
3328 </div>
3329 </div>
3330 </div>
3331
3332 <div class="vbo-overv-mngfest-block" style="display: none;">
3333 <div class="vbo-overv-mngfest-wrap">
3334 <div class="vbo-overlay-fests-list"></div>
3335 <div class="vbo-overlay-fests-addnew" data-ymd="">
3336 <h4><?php echo JText::translate('VBOADDCUSTOMFESTTODAY'); ?></h4>
3337 <div class="vbo-overlay-fests-addnew-elem">
3338 <label for="vbo-newfest-name"><?php echo JText::translate('VBPVIEWPLACESONE'); ?></label>
3339 <input type="text" id="vbo-newfest-name" value="" />
3340 </div>
3341 <div class="vbo-overlay-fests-addnew-elem">
3342 <label for="vbo-newfest-descr"><?php echo JText::translate('VBPLACEDESCR'); ?></label>
3343 <textarea id="vbo-newfest-descr"></textarea>
3344 </div>
3345 </div>
3346 </div>
3347 </div>
3348
3349 <div class="vbo-overv-mngroomdaynotes-block" style="display: none;">
3350 <div class="vbo-overv-mngroomdaynotes-wrap">
3351 <div class="vbo-modal-roomdaynotes-list"></div>
3352 <div class="vbo-modal-roomdaynotes-addnew" data-readymd="" data-ymd="" data-roomid="" data-subroomid="">
3353 <h4><?php echo JText::translate('VBOADDCUSTOMFESTTODAY'); ?></h4>
3354 <div class="vbo-modal-roomdaynotes-addnew-elem">
3355 <label for="vbo-newrdnote-name"><?php echo JText::translate('VBPVIEWPLACESONE'); ?></label>
3356 <input type="text" id="vbo-newrdnote-name" value="" />
3357 </div>
3358 <div class="vbo-modal-roomdaynotes-addnew-elem">
3359 <label for="vbo-newrdnote-descr"><?php echo JText::translate('VBPLACEDESCR'); ?></label>
3360 <textarea id="vbo-newrdnote-descr"></textarea>
3361 </div>
3362 <div class="vbo-modal-roomdaynotes-addnew-elem">
3363 <label for="vbo-newrdnote-cdays"><?php echo JText::translate('VBOCONSECUTIVEDAYS'); ?></label>
3364 <input type="number" id="vbo-newrdnote-cdays" min="0" max="365" value="0" onchange="vboRdayNoteCdaysCount();" onkeyup="vboRdayNoteCdaysCount();" />
3365 <span class="vbo-newrdnote-dayto">
3366 <span class="vbo-newrdnote-dayto-lbl"><?php echo JText::translate('VBOUNTIL'); ?></span>
3367 <span class="vbo-newrdnote-dayto-val"></span>
3368 </span>
3369 </div>
3370 </div>
3371 </div>
3372 </div>
3373