PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.7.0
VikBooking Hotel Booking Engine & PMS v1.7.0
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.7.0, at admin/views/overv/tmpl/default.php

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