PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
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
← All changes | admin/views/overv/tmpl/default.php +4460 -0 1.6.3 → 1.8.16 View file →
@@ -1,0 +1,4460 @@
1 +<?php
2 +/**
3 + * @package VikBooking
4 + * @subpackage com_vikbooking
5 + * @author Alessio Gaggii - E4J srl
6 + * @copyright Copyright (C) 2025 E4J srl. 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 +$app = JFactory::getApplication();
21 +$vbo_app = VikBooking::getVboApplication();
22 +
23 +// load context menu assets
24 +$vbo_app->loadContextMenuAssets();
25 +
26 +// prepare JS currency
27 +$vbo_app->prepareJavaScriptCurrency();
28 +
29 +$vbo_auth_pricing = JFactory::getUser()->authorise('core.vbo.pricing', 'com_vikbooking');
30 +
31 +JHtml::fetch('script', VBO_SITE_URI.'resources/jquery-ui.min.js');
32 +JHtml::fetch('script', VBO_ADMIN_URI.'resources/js_upload/jquery.stickytableheaders.min.js');
33 +
34 +$file_base = VBOPlatformDetection::isWordPress() ? 'admin.php' : 'index.php';
35 +
36 +// JS lang defs
37 +JText::script('VBSAVE');
38 +JText::script('VBPVIEWORDERSTHREE');
39 +JText::script('VBEDITORDERTHREE');
40 +JText::script('VBDAYS');
41 +JText::script('VBDAY');
42 +JText::script('VBMAILADULTS');
43 +JText::script('VBMAILADULT');
44 +JText::script('VBMAILCHILDREN');
45 +JText::script('VBMAILCHILD');
46 +JText::script('VBO_MISSING_SUBUNIT');
47 +JText::script('VBO_SHOW_CANCELLATIONS');
48 +JText::script('VBO_DRAG_SUBUNITS_SAMEDATE');
49 +JText::script('VBPVIEWORDERCHANNEL');
50 +JText::script('VBANNULLA');
51 +JText::script('VIKLOADING');
52 +JText::script('VBOVWDNDERRNOTENCELLS');
53 +JText::script('VBOVWDNDMOVINGBID');
54 +JText::script('VBOVWDNDMOVINGROOM');
55 +JText::script('VBOVWDNDMOVINGDATES');
56 +JText::script('VBEDITORDERROOMSNUM');
57 +JText::script('VBEDITORDERADULTS');
58 +JText::script('VBPICKUPAT');
59 +JText::script('VBRELEASEAT');
60 +JText::script('VBEDITORDERCHILDREN');
61 +JText::script('VBEDITORDERNINE');
62 +JText::script('VBPEDITBUSYTOTPAID');
63 +JText::script('VBOFEATASSIGNUNIT');
64 +JText::script('VBO_SPLIT_STAY');
65 +JText::script('VBO_CONF_SWAP_RNUMB');
66 +JText::script('VBO_ERR_SUBUN_MOVE_SUBUN');
67 +JText::script('VBO_BTYPE_OVERBOOKING');
68 +
69 +$days_labels = [
70 + JText::translate('VBSUN'),
71 + JText::translate('VBMON'),
72 + JText::translate('VBTUE'),
73 + JText::translate('VBWED'),
74 + JText::translate('VBTHU'),
75 + JText::translate('VBFRI'),
76 + JText::translate('VBSAT')
77 +];
78 +
79 +$nowdf = VikBooking::getDateFormat();
80 +if ($nowdf == "%d/%m/%Y") {
81 + $df = 'd/m/Y';
82 +} elseif ($nowdf == "%m/%d/%Y") {
83 + $df = 'm/d/Y';
84 +} else {
85 + $df = 'Y/m/d';
86 +}
87 +$currencysymb = VikBooking::getCurrencySymb();
88 +
89 +$pdebug = VikRequest::getInt('e4j_debug', '', 'request');
90 +$session = JFactory::getSession();
91 +$show_type = $session->get('vbUnitsShowType', '');
92 +$mnum = $session->get('vbOvwMnum', '1');
93 +$mnum = intval($mnum);
94 +$pcategory_id = $session->get('vbOvwCatid', 0);
95 +$cookie = $app->input->cookie;
96 +$cookie_uleft = $cookie->get('vboAovwUleft', '', 'string');
97 +$cookie_sticky_heads = $cookie->get('vboAovwStheads', 'off', 'string');
98 +$table_scroll_layout = $cookie->get('vboAovwScroll', (count($rows) > 3 ? 'inline' : 'tables'), 'string');
99 +$colortags = VikBooking::loadBookingsColorTags();
100 +$long_list = (count($rows) * $mnum) > 10;
101 +
102 +// View mode - Classic or Tags
103 +$pbmode = $session->get('vbTagsMode', 'classic');
104 +$tags_view_supported = false;
105 +
106 +// Room Units Distinctive Features
107 +$rooms_features_map = [];
108 +$rooms_features_bookings = [];
109 +$rooms_bids_pools = [];
110 +$room_bookings_pool = [];
111 +$bids_checkins = [];
112 +$index_loop = 0;
113 +foreach ($rows as $kr => $room) {
114 + if ($room['units'] <= 1) {
115 + $tags_view_supported = true;
116 + } else {
117 + if (!empty($room['params']) && $room['units'] <= 150) {
118 + //sub-room units only if room type has 150 units at most
119 + $room_params = json_decode($room['params'], true);
120 + if (is_array($room_params) && isset($room_params['features']) && is_array($room_params['features'])) {
121 + $rooms_features_map[$room['id']] = [];
122 + foreach ($room_params['features'] as $rind => $rfeatures) {
123 + foreach ($rfeatures as $fname => $fval) {
124 + if (strlen($fval)) {
125 + // $rooms_features_map[$room['id']][$rind] = '#'.$rind.' - '.JText::translate($fname).': '.$fval;
126 + $rooms_features_map[$room['id']][$rind] = "#{$rind} - {$fval}";
127 + break;
128 + }
129 + }
130 + }
131 + if (!$rooms_features_map[$room['id']]) {
132 + unset($rooms_features_map[$room['id']]);
133 + } else {
134 + foreach ($rooms_features_map[$room['id']] as $rind => $indexdata) {
135 + $clone_room = $room;
136 + $clone_room['unit_index'] = (int)$rind;
137 + $clone_room['unit_index_str'] = $indexdata;
138 + array_splice($rows, ($kr + 1 + $index_loop), 0, [$clone_room]);
139 + $index_loop++;
140 + }
141 +
142 + /**
143 + * Multi-unit room types with enough sub-units can display a "landing-unit-row" for
144 + * temporarily "parking" the bookings through drag & drop for an easier re-allocation.
145 + * Beside the holding area, we also add a row for the unassigned room bookings.
146 + *
147 + * @since 1.18.2 (J) - 1.8.2 (WP)
148 + * @since 1.18.3 (J) - 1.8.3 (WP) 2 sub-units are sufficient.
149 + */
150 + if ($room['units'] >= 2) {
151 + // set holding area row
152 + $clone_room['unit_index'] = 0;
153 + $clone_room['unit_index_str'] = JText::translate('VBO_HOLDING_AREA');
154 + array_splice($rows, ($kr + 1 + $index_loop), 0, [$clone_room]);
155 + $index_loop++;
156 +
157 + // set unassigned row
158 + $clone_room['unit_index'] = -1;
159 + $clone_room['unit_index_str'] = JText::translate('VBO_MISSING_SUBUNIT');
160 + array_splice($rows, ($kr + 1 + $index_loop), 0, [$clone_room]);
161 + $index_loop++;
162 + }
163 + }
164 + }
165 + }
166 + }
167 +}
168 +
169 +if (!$tags_view_supported) {
170 + $pbmode = 'classic';
171 +}
172 +
173 +// locked (stand-by) records
174 +$arrlocked = [];
175 +if (array_key_exists('tmplock', $arrbusy)) {
176 + if (count($arrbusy['tmplock']) > 0) {
177 + $arrlocked = $arrbusy['tmplock'];
178 + }
179 + unset($arrbusy['tmplock']);
180 +}
181 +
182 +/**
183 + * Global closing dates.
184 + *
185 + * @since 1.17.1 (J) - 1.7.1 (WP)
186 + */
187 +$globally_closed = VikBooking::getClosingDates();
188 +
189 +?>
190 +<script type="text/Javascript">
191 +function vboUnitsLeftOrBooked() {
192 + let set_to = jQuery('#uleftorbooked').val();
193 + jQuery('.vbo-overv-avcell[data-units-booked]').each(function() {
194 + let counter_el = jQuery(this).find('a');
195 + if (!counter_el.length) {
196 + return;
197 + }
198 + counter_el.first().text(jQuery(this).attr('data-'+set_to));
199 + });
200 + let nd = new Date();
201 + nd.setTime(nd.getTime() + (365*24*60*60*1000));
202 + document.cookie = "vboAovwUleft="+set_to+"; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
203 +}
204 +if (typeof jQuery.fn.tooltip === 'function') {
205 + jQuery(".hasTooltip").tooltip();
206 +} else {
207 + jQuery.fn.tooltip = function(){};
208 +}
209 +var vboFests = <?php echo json_encode($this->festivities); ?>;
210 +var vboRdayNotes = <?php echo json_encode($this->rdaynotes); ?>;
211 +</script>
212 +<form action="index.php?option=com_vikbooking&amp;task=overv" method="post" name="vboverview" class="vbo-avov-form">
213 + <div class="btn-toolbar vbo-avov-toolbar" id="filter-bar" style="width: 100%; display: inline-block;">
214 + <div class="btn-group pull-left">
215 + <?php echo $wmonthsel; ?>
216 + </div>
217 + <div class="btn-group pull-left">
218 + <select name="mnum" onchange="document.vboverview.submit();">
219 + <?php
220 + for($i = 1; $i <= 18; $i++) {
221 + ?>
222 + <option value="<?php echo $i; ?>"<?php echo $i == $mnum ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBOVWNUMMONTHS').' '.$i; ?></option>
223 + <?php
224 + }
225 + ?>
226 + </select>
227 + </div>
228 + <?php
229 + if (count($this->categories)) {
230 + ?>
231 + <div class="btn-group pull-left">
232 + <select name="category_id" onchange="document.vboverview.submit();">
233 + <option value="0">- <?php echo JText::translate('VBPVIEWROOMTWO'); ?> -</option>
234 + <?php
235 + foreach ($this->categories as $catid => $catname) {
236 + ?>
237 + <option value="<?php echo $catid; ?>"<?php echo $catid == $pcategory_id ? ' selected="selected"' : ''; ?>><?php echo $catname; ?></option>
238 + <?php
239 + }
240 + ?>
241 + </select>
242 + </div>
243 + <?php
244 + }
245 +
246 + $stickyheaders_cmd_on = '';
247 + $stickyheaders_cmd_off = '';
248 + if ($long_list) {
249 + if (VBOPlatformDetection::isJoomla()) {
250 + /**
251 + * @joomlaonly fixed offset selector is ".navbar"
252 + */
253 + $stickyheaders_cmd_on = "jQuery('table.vboverviewtable').stickyTableHeaders({cacheHeaderHeight: true, fixedOffset: (jQuery('.navbar').length ? jQuery('.navbar') : jQuery('#subhead-container'))});";
254 + } else {
255 + /**
256 + * @wponly fixed offset selector is "#wpadminbar"
257 + */
258 + $stickyheaders_cmd_on = "jQuery('table.vboverviewtable').stickyTableHeaders({cacheHeaderHeight: true, fixedOffset: jQuery('#wpadminbar')});";
259 + }
260 + $stickyheaders_cmd_off = "jQuery('table.vboverviewtable').stickyTableHeaders('destroy'); jQuery('th.bluedays').attr('style', '');";
261 + }
262 + ?>
263 + <script type="text/javascript">
264 + function vboToggleStickyTableHeaders(val) {
265 + let sticky_heads_cval = 'off';
266 + if (val > 0) {
267 + sticky_heads_cval = 'on';
268 + jQuery('table.vboverviewtable').addClass('vbo-overv-sticky-table-head-on');
269 + jQuery('table.vboverviewtable').removeClass('vbo-overv-sticky-table-head-off');
270 + <?php echo $stickyheaders_cmd_on; ?>
271 + // stop listening to the document scroll event to handle the fixed table heads
272 + document.removeEventListener('scroll', vboOvervHandleVScrollTableHeads);
273 + // scan all tables for listening to their horizontal scrolling event
274 + let tables = document.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
275 + tables.forEach((table) => {
276 + // stop listening to the table horizontal scroll events
277 + table.parentNode.removeEventListener('scroll', vboOvervHandleHScrollTableHead);
278 + });
279 + } else {
280 + jQuery('table.vboverviewtable').addClass('vbo-overv-sticky-table-head-off');
281 + jQuery('table.vboverviewtable').removeClass('vbo-overv-sticky-table-head-on');
282 + <?php echo $stickyheaders_cmd_off; ?>
283 + // listen to the document scroll event to handle the fixed table heads
284 + document.addEventListener('scroll', vboOvervHandleVScrollTableHeads);
285 + // scan all tables for listening to their horizontal scrolling event
286 + let tables = document.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
287 + tables.forEach((table) => {
288 + table.parentNode.addEventListener('scroll', vboOvervHandleHScrollTableHead);
289 + });
290 + }
291 + let nd = new Date();
292 + nd.setTime(nd.getTime() + (365*24*60*60*1000));
293 + document.cookie = "vboAovwStheads=" + sticky_heads_cval + "; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
294 + }
295 + </script>
296 + <?php
297 + if ($long_list || $tags_view_supported === true) {
298 + // group into an apposite context menu the layout and view types
299 + ?>
300 + <script type="text/javascript">
301 + const vboOvervLayoutBtns = [];
302 + <?php
303 + if ($long_list) {
304 + // push buttons to choose between the scroll or table layout
305 + ?>
306 + vboOvervLayoutBtns.push({
307 + class: 'btngroup',
308 + text: <?php echo json_encode(JText::translate('VBO_LAYOUT')); ?>,
309 + disabled: true,
310 + });
311 + vboOvervLayoutBtns.push({
312 + class: 'vbo-context-menu-entry-secondary',
313 + text: <?php echo json_encode(JText::translate('VBO_INLINE_SCROLL')); ?>,
314 + icon: '<?php echo $cookie_sticky_heads == 'off' && $table_scroll_layout == 'inline' ? VikBookingIcons::i('check-square') : VikBookingIcons::i('far fa-square'); ?>',
315 + action: (root, event) => {
316 + let nd = new Date();
317 + nd.setTime(nd.getTime() + (365*24*60*60*1000));
318 + document.cookie = "vboAovwScroll=inline; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
319 + vboToggleStickyTableHeaders(0);
320 + document.vboverview.submit();
321 + },
322 + });
323 + vboOvervLayoutBtns.push({
324 + class: 'vbo-context-menu-entry-secondary',
325 + text: <?php echo json_encode(JText::translate('VBO_SCROLL')); ?>,
326 + icon: '<?php echo $cookie_sticky_heads == 'off' && $table_scroll_layout != 'inline' ? VikBookingIcons::i('check-square') : VikBookingIcons::i('far fa-square'); ?>',
327 + action: (root, event) => {
328 + let nd = new Date();
329 + nd.setTime(nd.getTime() + (365*24*60*60*1000));
330 + document.cookie = "vboAovwScroll=tables; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
331 + vboToggleStickyTableHeaders(0);
332 + document.vboverview.submit();
333 + },
334 + });
335 + vboOvervLayoutBtns.push({
336 + class: 'vbo-context-menu-entry-secondary',
337 + text: <?php echo json_encode(JText::translate('VBO_TABLE')); ?>,
338 + icon: '<?php echo empty($cookie_sticky_heads) || $cookie_sticky_heads == 'on' ? VikBookingIcons::i('check-square') : VikBookingIcons::i('far fa-square'); ?>',
339 + action: (root, event) => {
340 + vboToggleStickyTableHeaders(1);
341 + document.vboverview.submit();
342 + },
343 + separator: <?php echo $tags_view_supported === true ? 'true' : 'false'; ?>,
344 + });
345 + <?php
346 + }
347 + if ($tags_view_supported === true) {
348 + // push buttons to choose between classic or tags view
349 + ?>
350 + vboOvervLayoutBtns.push({
351 + class: 'btngroup',
352 + text: <?php echo json_encode(JText::translate('VBMENUTHREE')); ?>,
353 + disabled: true,
354 + });
355 + vboOvervLayoutBtns.push({
356 + class: 'vbo-context-menu-entry-secondary',
357 + text: <?php echo json_encode(JText::translate('VBOAVOVWBMODECLASSIC')); ?>,
358 + icon: '<?php echo $pbmode != 'tags' ? VikBookingIcons::i('check-square') : VikBookingIcons::i('far fa-square'); ?>',
359 + action: (root, event) => {
360 + let inp = document.createElement('input');
361 + inp.setAttribute('type', 'hidden');
362 + inp.setAttribute('name', 'bmode');
363 + inp.value = 'classic';
364 + document.vboverview.append(inp);
365 + document.vboverview.submit();
366 + },
367 + });
368 + vboOvervLayoutBtns.push({
369 + class: 'vbo-context-menu-entry-secondary',
370 + text: <?php echo json_encode(JText::translate('VBOAVOVWBMODETAGS')); ?>,
371 + icon: '<?php echo $pbmode == 'tags' ? VikBookingIcons::i('check-square') : VikBookingIcons::i('far fa-square'); ?>',
372 + action: (root, event) => {
373 + let inp = document.createElement('input');
374 + inp.setAttribute('type', 'hidden');
375 + inp.setAttribute('name', 'bmode');
376 + inp.value = 'tags';
377 + document.vboverview.append(inp);
378 + document.vboverview.submit();
379 + },
380 + });
381 + <?php
382 + }
383 + ?>
384 +
385 + jQuery(function() {
386 +
387 + // define the context menu for the actions button
388 + jQuery('.vbo-context-menu-overview-layout').vboContextMenu({
389 + placement: 'bottom-left',
390 + buttons: vboOvervLayoutBtns,
391 + });
392 +
393 + });
394 + </script>
395 +
396 + <div class="btn-group pull-left">
397 + <button type="button" class="btn vbo-context-menu-btn vbo-context-menu-btn-raw vbo-context-menu-overview-layout">
398 + <span class="vbo-context-menu-lbl"><?php echo JText::translate('VBO_LAYOUT'); ?></span>
399 + <span class="vbo-context-menu-ico"><?php VikBookingIcons::e('sort-down'); ?></span>
400 + </button>
401 + </div>
402 + <?php
403 + }
404 +
405 + ?>
406 + <div class="btn-group pull-left">
407 + <?php
408 + // display overview context-menu
409 + echo JLayoutHelper::render('overview.actions', ['caller' => 'overv', 'rooms' => $rows]);
410 + ?>
411 + </div>
412 + <div class="btn-group pull-right">
413 + <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>
414 + </div>
415 + <div class="btn-group pull-right vbo-avov-legend">
416 + <!-- <span class="vbo-overview-legend-init"><?php echo JText::translate('VBOVERVIEWLEGEND'); ?></span> -->
417 + <div class="vbo-overview-legend-red">
418 + <span class="vbo-overview-legend-box">&nbsp;</span>
419 + <span class="vbo-overview-legend-title"><?php echo JText::translate('VBOVERVIEWLEGRED'); ?></span>
420 + </div>
421 + <div class="vbo-overview-legend-yellow">
422 + <span class="vbo-overview-legend-box">&nbsp;</span>
423 + <span class="vbo-overview-legend-title"><?php echo JText::translate('VBOVERVIEWLEGYELLOW'); ?></span>
424 + </div>
425 + <div class="vbo-overview-legend-green">
426 + <span class="vbo-overview-legend-box">&nbsp;</span>
427 + <span class="vbo-overview-legend-title"><?php echo JText::translate('VBOVERVIEWLEGGREEN'); ?></span>
428 + </div>
429 + <div class="vbo-overview-legend-green vbo-overview-legend-dnd">
430 + <span class="vbo-overview-legend-box"><i class="vboicn-enlarge" style="margin: 1px; display: block; text-align: center;"></i></span>
431 + <span class="vbo-overview-legend-title"><?php echo JText::translate('VBOVERVIEWLEGDND'); ?></span>
432 + </div>
433 + </div>
434 + </div>
435 +<?php
436 +// propagate the current task manager filters
437 +foreach ((array) $app->input->get('tmfilters', [], 'array') as $tm_filter_key => $tm_filter_val) {
438 + if (is_scalar($tm_filter_val)) {
439 + ?>
440 + <input type="hidden" name="tmfilters[<?php echo $tm_filter_key; ?>]" value="<?php echo JHtml::fetch('esc_attr', $tm_filter_val); ?>" />
441 + <?php
442 + } elseif (is_array($tm_filter_val) && $tm_filter_val && array_keys($tm_filter_val) == range(0, count($tm_filter_val) - 1)) {
443 + // add support for linear arrays
444 + foreach ($tm_filter_val as $tm_filter_val_sub) {
445 + ?>
446 + <input type="hidden" name="tmfilters[<?php echo $tm_filter_key; ?>][]" value="<?php echo JHtml::fetch('esc_attr', $tm_filter_val_sub); ?>" />
447 + <?php
448 + }
449 + }
450 +}
451 +?>
452 +</form>
453 +
454 +<?php
455 +$todayymd = date('Y-m-d');
456 +$last_displayed_day_ts = strtotime(date('Y-m-t 23:59:59', strtotime(sprintf('+%d %s', $mnum - 1, (($mnum - 1) === 1 ? 'month' : 'months')), $tsstart)));
457 +$last_displayed_day_info = getdate($last_displayed_day_ts);
458 +$nowts = getdate($tsstart);
459 +$curts = $nowts;
460 +for ($mind = 1; $mind <= ($table_scroll_layout === 'inline' ? 1 : $mnum); $mind++) {
461 + $monthname = VikBooking::sayMonth($curts['mon']);
462 + $month_names_cache = [
463 + $curts['mon'] => $monthname,
464 + ];
465 + $displayMonthLabel = sprintf('%s %d', $monthname, $curts['year']);
466 + if ($table_scroll_layout === 'inline') {
467 + $startMonthName = VikBooking::sayMonth($nowts['mon'], true);
468 + $endMonthName = VikBooking::sayMonth($last_displayed_day_info['mon'], true);
469 + if ($nowts['year'] != $last_displayed_day_info['year']) {
470 + $displayMonthLabel = sprintf('%s %d - %s %d', $startMonthName, $nowts['year'], $endMonthName, $last_displayed_day_info['year']);
471 + } else {
472 + if ($mnum > 1) {
473 + $displayMonthLabel = sprintf('%s - %s %d', $startMonthName, $endMonthName, $last_displayed_day_info['year']);
474 + } else {
475 + $displayMonthLabel = sprintf('%s %d', $monthname, $last_displayed_day_info['year']);
476 + }
477 + }
478 + }
479 + ?>
480 +<div class="vbo-overv-montable-wrap">
481 + <div class="vbo-table-responsive">
482 + <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); ?>" data-month-from="<?php echo date('Y-m-01', $curts[0]); ?>" data-month-to="<?php echo $table_scroll_layout === 'inline' ? date('Y-m-d', $last_displayed_day_ts) : date('Y-m-t', $curts[0]); ?>">
483 + <thead>
484 + <tr class="vboverviewtablerowone">
485 + <th class="bluedays skip-bluedays-click vbo-overview-month" data-initial-period="<?php echo JHtml::fetch('esc_attr', $displayMonthLabel); ?>"><?php echo $displayMonthLabel; ?></th>
486 + <?php
487 + $moncurts = $curts;
488 + $mon = $moncurts['mon'];
489 + // loop until the end of the range of dates, or until the end of the current month
490 + while (($table_scroll_layout === 'inline' && $moncurts[0] < $last_displayed_day_ts) || ($table_scroll_layout !== 'inline' && $moncurts['mon'] == $mon)) {
491 + $curdayymd = date('Y-m-d', $moncurts[0]);
492 + if (!($month_names_cache[$moncurts['mon']] ?? null)) {
493 + $month_names_cache[$moncurts['mon']] = VikBooking::sayMonth($moncurts['mon']);
494 + }
495 + $read_day = $days_labels[$moncurts['wday']] . ' ' . $moncurts['mday'] . ' ' . $month_names_cache[$moncurts['mon']] . ' ' . $curts['year'];
496 + $displayDayLabel = $days_labels[$moncurts['wday']];
497 + $headCellClasses = [
498 + 'bluedays',
499 + ($todayymd == $curdayymd ? 'vbo-overv-todaycell' : null),
500 + (isset($this->festivities[$curdayymd]) ? 'vbo-overv-festcell' : null),
501 + ];
502 + if ($moncurts['mday'] == 1 && $table_scroll_layout === 'inline') {
503 + $displayDayLabel = sprintf('%s, %s', VikBooking::sayMonth($moncurts['mon'], true), $days_labels[$moncurts['wday']]);
504 + if ($nowts['mon'] != $moncurts['mon']) {
505 + $headCellClasses[] = 'vbo-overv-cell-newmonth';
506 + }
507 + }
508 + ?>
509 + <th class="<?php echo implode(' ', array_filter($headCellClasses)); ?>" data-ymd="<?php echo $curdayymd; ?>" data-readymd="<?php echo $read_day; ?>">
510 + <span class="vbo-overw-tablewday"><?php echo $displayDayLabel; ?></span>
511 + <span class="vbo-overw-tablemday"><?php echo $moncurts['mday']; ?></span>
512 + </th>
513 + <?php
514 + // parse the next day
515 + $moncurts = getdate(mktime(0, 0, 0, $moncurts['mon'], ($moncurts['mday'] + 1), $moncurts['year']));
516 + }
517 + ?>
518 + </tr>
519 + </thead>
520 + <tbody>
521 + <?php
522 + foreach ($rows as $room) {
523 + $moncurts = $curts;
524 + $mon = $moncurts['mon'];
525 + $room_tags_view = $pbmode == 'tags' && $tags_view_supported === true && $room['units'] <= 1 ? true : false;
526 + $is_subunit = array_key_exists('unit_index', $room);
527 + $is_subunit_tmp_row = $is_subunit && empty($room['unit_index']);
528 + $is_subunit_unassigned_row = $is_subunit && ($room['unit_index'] ?? 0) == -1;
529 +
530 + // build the list of classes for the current room-row
531 + $row_classes = [
532 + 'vboverviewtablerow',
533 + ($is_subunit ? 'vboverviewtablerow-subunit' : ''),
534 + ($is_subunit_tmp_row ? 'vboverviewtablerow-subunit-tmp' : ''),
535 + ($is_subunit_unassigned_row ? 'vboverviewtablerow-subunit-unassigned' : ''),
536 + (($is_subunit || $room['units'] == 1) && $cookie_sticky_heads == 'off' ? 'vbo-overv-row-snake' : ''),
537 + ];
538 +
539 + // build the main cell icon identifier
540 + $main_cell_icn_id = 'bed';
541 + if ($is_subunit_tmp_row) {
542 + $main_cell_icn_id = 'parking';
543 + } elseif ($is_subunit_unassigned_row) {
544 + $main_cell_icn_id = 'exclamation-triangle';
545 + }
546 +
547 + ?>
548 + <tr class="<?php echo implode(' ', array_filter($row_classes)); ?>"<?php echo !$is_subunit ? ' data-roomid="' . $room['id'] . '"' : ''; ?><?php echo $is_subunit ? ' data-subroomid="' . $room['id'] . '-' . $room['unit_index'] . '"' : ''; ?><?php echo $is_subunit_tmp_row ? ' style="display: none;"' : ''; ?>>
549 + <?php
550 + if ($is_subunit) {
551 + ?>
552 + <td class="roomname subroomname" data-roomid="<?php echo '-' . $room['id']; ?>">
553 + <span class="vbo-overview-subroomunits"><?php VikBookingIcons::e($main_cell_icn_id); ?></span>
554 + <span class="vbo-overview-subroomname"><?php echo $room['unit_index_str']; ?></span>
555 + </td>
556 + <?php
557 + } else {
558 + ?>
559 + <td class="roomname" data-roomid="<?php echo $room['id']; ?>" data-units="<?php echo $room['units']; ?>">
560 + <span class="vbo-overview-room-info">
561 + <span class="vbo-overview-roomunits"><?php echo $room['units']; ?></span>
562 + <span class="vbo-overview-roomname"><?php echo $room['name']; ?></span>
563 + <?php
564 + if (isset($rooms_features_map[$room['id']])) {
565 + ?>
566 + <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>
567 + <?php
568 + }
569 + ?>
570 + </span>
571 + </td>
572 + <?php
573 + }
574 +
575 + // loop until the end of the range of dates, or until the end of the current month
576 + $room_bids_pool = [];
577 + $room_bookings = [];
578 + while (($table_scroll_layout === 'inline' && $moncurts[0] < $last_displayed_day_ts) || ($table_scroll_layout !== 'inline' && $moncurts['mon'] == $mon)) {
579 + // init cell values
580 + $dclass = 'vbo-grid-avcell vbo-overv-avcell ' . (!$is_subunit ? "notbusy" : "subnotbusy");
581 + $is_checkin = false;
582 + $is_sharedcal = false;
583 + $is_closure = false;
584 + $lastbidcheckout = null;
585 + $dalt = "";
586 + $bid = "";
587 + $bids_pool = [];
588 + $totfound = 0;
589 + $prev_day_key = date('Y-m-d', strtotime('-1 day', $moncurts[0]));
590 + $cur_day_key = date('Y-m-d', $moncurts[0]);
591 +
592 + // check for global closing dates
593 + foreach ($globally_closed as $glob_closed) {
594 + if ($moncurts[0] >= $glob_closed['from'] && $moncurts[0] <= $glob_closed['to']) {
595 + $dclass .= ' vbo-overv-globally-closed';
596 + }
597 + }
598 +
599 + // check availability
600 + if (!empty($arrbusy[$room['id']]) && !$is_subunit) {
601 + foreach ($arrbusy[$room['id']] as $b) {
602 + $tmpone = getdate($b['checkin']);
603 + $ritts = mktime(0, 0, 0, $tmpone['mon'], $tmpone['mday'], $tmpone['year']);
604 + $tmptwo = getdate($b['checkout']);
605 + $conts = mktime(0, 0, 0, $tmptwo['mon'], $tmptwo['mday'], $tmptwo['year']);
606 + if (!($moncurts[0] >= $ritts && $moncurts[0] < $conts)) {
607 + // booking does not involve the current day
608 + continue;
609 + }
610 + $dclass = "vbo-grid-avcell vbo-overv-avcell busy";
611 + $bid = $b['idorder'];
612 + $is_sharedcal = !empty($b['sharedcal']) ? true : $is_sharedcal;
613 + $is_closure = !empty($b['closure']) ? true : $is_closure;
614 + $bid_str = '-' . $bid . '-';
615 + if (!in_array($bid_str, $bids_pool)) {
616 + $bids_pool[] = $bid_str;
617 + }
618 + if (isset($rooms_features_map[$room['id']])) {
619 + // multi-unit room with distinctive features defined
620 + if (!isset($room_bids_pool[$cur_day_key])) {
621 + $room_bids_pool[$cur_day_key] = [];
622 + $room_bookings[$cur_day_key] = [];
623 + }
624 + $room_bids_pool[$cur_day_key][] = (int)$bid;
625 + $room_bookings[$cur_day_key][] = $b;
626 + } elseif ($room['units'] == 1) {
627 + // single-unit room
628 + if (!isset($room_bookings[$cur_day_key])) {
629 + $room_bookings[$cur_day_key] = [];
630 + }
631 + $room_bookings[$cur_day_key][] = $b;
632 + }
633 + if ($moncurts[0] == $ritts) {
634 + $dalt = JText::translate('VBPICKUPAT')." ".date('H:i', $b['checkin']);
635 + $is_checkin = true;
636 + $lastbidcheckout = $b['checkout'];
637 + $bids_checkins[$bid] = $bids_checkins[$bid] ?? [];
638 + $bids_checkins[$bid][] = $cur_day_key;
639 + } elseif ($moncurts[0] == $conts) {
640 + $dalt = JText::translate('VBRELEASEAT')." ".date('H:i', $b['checkout']);
641 + }
642 + $totfound++;
643 + }
644 + }
645 +
646 + // locked (stand-by) records
647 + if ($room_tags_view === true && isset($arrlocked[$room['id']]) && $arrlocked[$room['id']] && !$is_subunit) {
648 + foreach ($arrlocked[$room['id']] as $l) {
649 + $tmpone = getdate($l['checkin']);
650 + $ritts = mktime(0, 0, 0, $tmpone['mon'], $tmpone['mday'], $tmpone['year']);
651 + $tmptwo = getdate($l['checkout']);
652 + $conts = mktime(0, 0, 0, $tmptwo['mon'], $tmptwo['mday'], $tmptwo['year']);
653 + if ($moncurts[0] >= $ritts && $moncurts[0] < $conts) {
654 + $dclass = strpos($dclass, "notbusy") !== false ? "busytmplock" : "busy busytmplock";
655 + $bid = $l['idorder'];
656 + if (!in_array($bid, $bids_pool)) {
657 + if (count($bids_pool) > 0) {
658 + array_unshift($bids_pool, '-'.$bid.'-');
659 + } else {
660 + $bids_pool[] = '-'.$bid.'-';
661 + }
662 + }
663 + if ($moncurts[0] == $ritts) {
664 + $dalt = JText::translate('VBPICKUPAT')." ".date('H:i', $l['checkin']);
665 + } elseif ($moncurts[0] == $conts) {
666 + $dalt = JText::translate('VBRELEASEAT')." ".date('H:i', $l['checkout']);
667 + }
668 + $totfound++;
669 + }
670 + }
671 + }
672 +
673 + // handle single-unit room reservations
674 + if ($room['units'] == 1 && !isset($rooms_features_map[$room['id']]) && isset($room_bookings[$cur_day_key]) && $room_bookings[$cur_day_key]) {
675 + // set the first (and only) booking
676 + $day_booking = [];
677 + foreach ($room_bookings[$cur_day_key] as $day_res) {
678 + if (!$day_res['closure'] && !$day_res['sharedcal']) {
679 + $day_booking = $day_res;
680 + break;
681 + }
682 + }
683 + if ($day_booking) {
684 + if (!isset($room_bookings_pool[$room['id']])) {
685 + $room_bookings_pool[$room['id']] = [];
686 + }
687 + if (!isset($room_bookings_pool[$room['id']][$cur_day_key])) {
688 + $room_bookings_pool[$room['id']][$cur_day_key] = [];
689 + }
690 + $room_bookings_pool[$room['id']][$cur_day_key][0] = $day_booking;
691 + }
692 + }
693 +
694 + $useday = $moncurts['mday'] < 10 ? "0{$moncurts['mday']}" : $moncurts['mday'];
695 + $dclass .= $totfound < $room['units'] && $totfound > 0 ? ' vbo-partially' : '';
696 + $dclass .= $is_sharedcal ? ' busy-sharedcalendar' : '';
697 + $dclass .= $is_closure ? ' busy-closure' : '';
698 + $dstyle = '';
699 + $astyle = '';
700 + if ($room_tags_view === true && $totfound > 0) {
701 + $last_bid = intval(str_replace('-', '', $bids_pool[(count($bids_pool) - 1)]));
702 + $binfo = VikBooking::getBookingInfoFromID($last_bid);
703 + if ($binfo) {
704 + $bcolortag = VikBooking::applyBookingColorTag($binfo);
705 + if ($bcolortag) {
706 + $bcolortag['name'] = JText::translate($bcolortag['name']);
707 + $dstyle = " style=\"background-color: ".$bcolortag['color']."; color: ".(array_key_exists('fontcolor', $bcolortag) ? $bcolortag['fontcolor'] : '#ffffff').";\" data-lastbid=\"".$last_bid."\"";
708 + $astyle = " style=\"color: ".(array_key_exists('fontcolor', $bcolortag) ? $bcolortag['fontcolor'] : '#ffffff').";\"";
709 + $dclass .= ' vbo-hascolortag';
710 + }
711 + }
712 + }
713 + if ($is_subunit && isset($rooms_bids_pools[$room['id']][$cur_day_key]) && isset($rooms_features_bookings[$room['id']][$room['unit_index']]) && isset($room_bookings_pool[$room['id']][$cur_day_key][$room['unit_index']])) {
714 + foreach ($rooms_bids_pools[$room['id']][$cur_day_key] as $bid) {
715 + $bid = intval(str_replace('-', '', $bid));
716 + if (in_array($bid, $rooms_features_bookings[$room['id']][$room['unit_index']])) {
717 + $room['units'] = 1;
718 + $totfound = 1;
719 + $dclass = "vbo-grid-avcell vbo-overv-avcell subroom-busy";
720 + $is_checkin = isset($bids_checkins[$bid]) && in_array($cur_day_key, $bids_checkins[$bid]) ? true : $is_checkin;
721 + if (count($room_bookings_pool[$room['id']][$cur_day_key]) > 1 && !empty($room_bookings_pool[$room['id']][$cur_day_key][$room['unit_index']]['checkin'])) {
722 + // ensure multi-room bookings display the proper check-in day in case of different nights of stay
723 + $is_checkin = date('Y-m-d', $room_bookings_pool[$room['id']][$cur_day_key][$room['unit_index']]['checkin']) == $cur_day_key;
724 + }
725 +
726 + /**
727 + * In case of mixed room-types (hotels inventory and listings), we allow
728 + * the tags to be applied on sub-units for multi-unit room types.
729 + *
730 + * @since 1.17.5 (J) - 1.7.5 (WP)
731 + */
732 + if ($tags_view_supported && $binfo = VikBooking::getBookingInfoFromID($bid)) {
733 + $bcolortag = VikBooking::applyBookingColorTag($binfo);
734 + if ($bcolortag) {
735 + $bcolortag['name'] = JText::translate($bcolortag['name']);
736 + $dstyle = " style=\"background-color: ".$bcolortag['color']."; color: ".(array_key_exists('fontcolor', $bcolortag) ? $bcolortag['fontcolor'] : '#ffffff').";\" data-lastbid=\"".$bid."\"";
737 + $astyle = " style=\"color: ".(array_key_exists('fontcolor', $bcolortag) ? $bcolortag['fontcolor'] : '#ffffff').";\"";
738 + $dclass .= ' vbo-hascolortag';
739 + }
740 + }
741 +
742 + // abort
743 + break;
744 + }
745 + }
746 + }
747 + $write_units = $show_type == 'units-left' || (!empty($cookie_uleft) && $cookie_uleft == 'units-left') ? ($room['units'] - $totfound) : $totfound;
748 + // check today's date
749 + $curdayymd = date('Y-m-d', $moncurts[0]);
750 + if ($todayymd == $curdayymd) {
751 + $dclass .= ' vbo-overv-todaycell';
752 + }
753 + if (isset($this->festivities[$curdayymd])) {
754 + $dclass .= ' vbo-overv-festcell';
755 + }
756 +
757 + /**
758 + * Critical dates defined at room-day level.
759 + *
760 + * @since 1.13.5 (J) - 1.3.5 (WP)
761 + */
762 + $rdaynote_keyid = $cur_day_key . '_' . $room['id'] . '_' . (isset($room['unit_index']) ? $room['unit_index'] : '0');
763 + if (isset($this->rdaynotes[$rdaynote_keyid])) {
764 + // note exists for this combination of date, room ID and subunit
765 + $dclass .= ' vbo-roomdaynote-full';
766 + $rdaynote_icn = 'sticky-note';
767 + } else {
768 + // no notes for this cell
769 + $dclass .= ' vbo-roomdaynote-empty';
770 + $rdaynote_icn = 'far fa-sticky-note';
771 + }
772 + $critical_note = '<span class="vbo-roomdaynote-trigger" data-roomday="' . $rdaynote_keyid . '"><i class="' . VikBookingIcons::i($rdaynote_icn, 'vbo-roomdaynote-display') . '"></i></span>';
773 + if ($is_subunit_tmp_row || $is_subunit_unassigned_row) {
774 + $critical_note = '';
775 + }
776 +
777 + /**
778 + * Closures overlapping one real reservation for a single-unit room should still
779 + * allow the display of the booking snake if it's just one real reservation.
780 + */
781 + $closure_on_real_res = false;
782 + if ($totfound === 2 && !$is_subunit && $room['units'] == 1 && $cookie_sticky_heads == 'off' && strpos($dclass, "busy-closure") !== false) {
783 + if (isset($room_bookings_pool[$room['id']]) && isset($room_bookings_pool[$room['id']][$cur_day_key]) && $room_bookings_pool[$room['id']][$cur_day_key]) {
784 + if (isset($room_bookings_pool[$room['id']][$cur_day_key][0]) && !$room_bookings_pool[$room['id']][$cur_day_key][0]['closure']) {
785 + // found a real booking on a date with at least one closure
786 + $closure_on_real_res = true;
787 + }
788 + }
789 + }
790 +
791 + // prepare cell content
792 + $day_has_closure = false;
793 + if ($totfound === 1 || $closure_on_real_res) {
794 + // start booking snake content
795 + $day_booking_snake = '';
796 + // collect the current day booking data
797 + $day_booking_data = [];
798 + if (isset($room_bookings_pool[$room['id']][$cur_day_key]) && $room_bookings_pool[$room['id']][$cur_day_key]) {
799 + if ($is_subunit && isset($room_bookings_pool[$room['id']][$cur_day_key][$room['unit_index']])) {
800 + $day_booking_data = $room_bookings_pool[$room['id']][$cur_day_key][$room['unit_index']];
801 + } elseif (!$is_subunit && isset($room_bookings_pool[$room['id']][$cur_day_key][0])) {
802 + $day_booking_data = $room_bookings_pool[$room['id']][$cur_day_key][0];
803 + }
804 + }
805 +
806 + // whether we need to prepend a check-out snake for the last reservation
807 + if (isset($room_bookings_pool[$room['id']][$prev_day_key]) && $room_bookings_pool[$room['id']][$prev_day_key]) {
808 + if ($is_subunit && isset($room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']])) {
809 + if (date('Y-m-d', $room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']]['checkout']) == $cur_day_key) {
810 + // prepend checkout snake
811 + $day_booking_snake .= '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
812 + }
813 + } elseif (!$is_subunit && $room['units'] == 1 && isset($room_bookings_pool[$room['id']][$prev_day_key][0])) {
814 + if (date('Y-m-d', $room_bookings_pool[$room['id']][$prev_day_key][0]['checkout']) == $cur_day_key) {
815 + // prepend checkout snake
816 + $day_booking_snake .= '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
817 + }
818 + }
819 + }
820 +
821 + // update if the current day is actually a closure
822 + $day_has_closure = $day_has_closure || !empty($day_booking_data['closure']);
823 +
824 + if ($room['units'] == 1 && $day_booking_data && !$day_booking_data['closure']) {
825 + // build tableaux-style snake container for guest
826 + $customer_descr = '';
827 + if ($is_checkin) {
828 + // customer details
829 + if (!empty($day_booking_data['first_name']) || !empty($day_booking_data['last_name'])) {
830 + // check if we need to display a profile picture or a channel logo
831 + $booking_avatar_src = null;
832 + $booking_avatar_alt = null;
833 + if (!empty($day_booking_data['pic'])) {
834 + // customer profile picture
835 + $booking_avatar_src = strpos($day_booking_data['pic'], 'http') === 0 ? $day_booking_data['pic'] : VBO_SITE_URI . 'resources/uploads/' . $day_booking_data['pic'];
836 + $booking_avatar_alt = basename($booking_avatar_src);
837 + } elseif (!empty($day_booking_data['idorderota']) && !empty($day_booking_data['channel'])) {
838 + // channel logo
839 + $logo_helper = VikBooking::getVcmChannelsLogo($day_booking_data['channel'], $get_istance = true);
840 + if ($logo_helper !== false) {
841 + $booking_avatar_src = $logo_helper->getSmallLogoURL();
842 + $booking_avatar_alt = $logo_helper->provenience;
843 + }
844 + }
845 +
846 + if (!empty($booking_avatar_src)) {
847 + // make sure the alt attribute is not too long in case of broken images
848 + $booking_avatar_alt = !empty($booking_avatar_alt) && strlen($booking_avatar_alt) > 15 ? '...' . substr($booking_avatar_alt, -12) : $booking_avatar_alt;
849 + // append booking avatar image
850 + $customer_descr .= '<span class="vbo-tableaux-booking-avatar"><img src="' . $booking_avatar_src . '" class="vbo-tableaux-booking-avatar-img" decoding="async" loading="lazy" ' . (!empty($booking_avatar_alt) ? 'alt="' . htmlspecialchars($booking_avatar_alt) . '" ' : '') . '/></span>';
851 + }
852 +
853 + // customer name
854 + $customer_fullname = trim($day_booking_data['first_name'] . ' ' . $day_booking_data['last_name']);
855 + if (strlen($customer_fullname) > 26) {
856 + if (function_exists('mb_substr')) {
857 + $customer_fullname = trim(mb_substr($customer_fullname, 0, 26, 'UTF-8')) . '..';
858 + } else {
859 + $customer_fullname = trim(substr($customer_fullname, 0, 26)) . '..';
860 + }
861 + }
862 + $customer_descr .= '<span class="vbo-tableaux-guest-name">' . $customer_fullname . '</span>';
863 + } else {
864 + // parse the customer data string
865 + $custdata_parts = explode("\n", $day_booking_data['custdata']);
866 + $enoughinfo = false;
867 + if (count($custdata_parts) > 2 && strpos($custdata_parts[0], ':') !== false && strpos($custdata_parts[1], ':') !== false) {
868 + // get the first two fields
869 + $custvalues = array();
870 + foreach ($custdata_parts as $custdet) {
871 + if (strlen($custdet) < 1) {
872 + continue;
873 + }
874 + $custdet_parts = explode(':', $custdet);
875 + if (count($custdet_parts) >= 2) {
876 + unset($custdet_parts[0]);
877 + array_push($custvalues, trim(implode(':', $custdet_parts)));
878 + }
879 + if (count($custvalues) > 1) {
880 + break;
881 + }
882 + }
883 + if (count($custvalues) > 1) {
884 + $enoughinfo = true;
885 + $customer_nominative = trim(implode(' ', $custvalues));
886 + if (strlen($customer_nominative) > 26) {
887 + if (function_exists('mb_substr')) {
888 + $customer_nominative = trim(mb_substr($customer_nominative, 0, 26, 'UTF-8')) . '..';
889 + } else {
890 + $customer_nominative = trim(substr($customer_nominative, 0, 26)) . '..';
891 + }
892 + }
893 + if (!empty($day_booking_data['idorderota']) && !empty($day_booking_data['channel'])) {
894 + // add support for the channel logo for the imported OTA reservations with no customer record
895 + $logo_helper = VikBooking::getVcmChannelsLogo($day_booking_data['channel'], $get_istance = true);
896 + if ($logo_helper !== false) {
897 + $booking_avatar_src = $logo_helper->getSmallLogoURL();
898 + $booking_avatar_alt = $logo_helper->provenience;
899 + // make sure the alt attribute is not too long in case of broken images
900 + $booking_avatar_alt = !empty($booking_avatar_alt) && strlen($booking_avatar_alt) > 15 ? '...' . substr($booking_avatar_alt, -12) : $booking_avatar_alt;
901 + // append booking avatar image
902 + $customer_descr .= '<span class="vbo-tableaux-booking-avatar"><img src="' . $booking_avatar_src . '" class="vbo-tableaux-booking-avatar-img" decoding="async" loading="lazy" ' . (!empty($booking_avatar_alt) ? 'alt="' . htmlspecialchars($booking_avatar_alt) . '" ' : '') . '/></span>';
903 + }
904 + }
905 + // set customer nominative built
906 + $customer_descr .= '<span class="vbo-tableaux-guest-name">' . $customer_nominative . '</span>';
907 + }
908 + }
909 + if (!$enoughinfo) {
910 + $customer_descr .= '<span class="vbo-tableaux-guest-name">#' . $day_booking_data['idorder'] . '</span>';
911 + }
912 + }
913 + }
914 + // set value
915 + $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>';
916 + }
917 +
918 + if ($cookie_sticky_heads != 'off') {
919 + // not supported with the sticky table headers layout
920 + $day_booking_snake = '';
921 + }
922 +
923 + $write_units = strpos($dclass, 'subroom-busy') !== false ? '' : $write_units;
924 + if ($table_scroll_layout === 'inline') {
925 + // one table for the whole range, determine if dragging this cell would move a non-displayed check-out
926 + $stopdrag = $lastbidcheckout && $lastbidcheckout > $last_displayed_day_ts;
927 + // check if we are displaying the first day of a new month
928 + if ($moncurts['mday'] == 1 && $nowts['mon'] != $moncurts['mon']) {
929 + $dclass .= ' vbo-overv-cell-newmonth';
930 + }
931 + } else {
932 + // one table per month, determine if dragging this cell would move a non-displayed check-out
933 + $stopdrag = ($mind == $mnum && !is_null($lastbidcheckout) && (int) date('n', $lastbidcheckout) != (int) $mon);
934 + }
935 + $dclass .= $is_checkin === true ? ' vbo-checkinday' : '';
936 +
937 + // build proper link
938 + if ($totfound > 1) {
939 + // there must be a closure overlapping a real booking
940 + $cell_link = 'index.php?option=com_vikbooking&task=choosebusy&idroom=' . $room['id'] . '&ts=' . $moncurts[0] . '&goto=overv';
941 + } else {
942 + // regular situation with one booking only
943 + $cell_link = 'index.php?option=com_vikbooking&task=editbusy&cid[]=' . $bid . '&goto=overv';
944 + }
945 + ?>
946 + <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); ?>">
947 + <?php
948 + if (!$day_booking_snake) {
949 + if ($is_checkin === true && !$stopdrag && !$day_has_closure && !$is_subunit_unassigned_row) {
950 + ?>
951 + <span class="vbo-draggable-sp" draggable="true">
952 + <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>
953 + </span>
954 + <?php
955 + } else {
956 + ?>
957 + <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>
958 + <?php
959 + }
960 + }
961 + echo $day_booking_snake . $critical_note;
962 + ?>
963 + </td>
964 + <?php
965 + } elseif ($totfound > 1) {
966 + if ($table_scroll_layout === 'inline') {
967 + // check if we are displaying the first day of a new month
968 + if ($moncurts['mday'] == 1 && $nowts['mon'] != $moncurts['mon']) {
969 + $dclass .= ' vbo-overv-cell-newmonth';
970 + }
971 + }
972 + ?>
973 + <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); ?>">
974 + <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>
975 + <?php echo $critical_note; ?>
976 + </td>
977 + <?php
978 + } else {
979 + // no booked records
980 + if ($table_scroll_layout === 'inline') {
981 + // check if we are displaying the first day of a new month
982 + if ($moncurts['mday'] == 1 && $nowts['mon'] != $moncurts['mon']) {
983 + $dclass .= ' vbo-overv-cell-newmonth';
984 + }
985 + }
986 + ?>
987 + <td align="center" class="<?php echo $dclass; ?>" data-day="<?php echo $cur_day_key; ?>" data-bids="">
988 + <?php
989 + 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]) {
990 + if ($is_subunit && isset($room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']])) {
991 + if (date('Y-m-d', $room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']]['checkout']) == $cur_day_key && !($room_bookings_pool[$room['id']][$prev_day_key][$room['unit_index']]['closure'] ?? 0)) {
992 + // prepend checkout snake
993 + echo '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
994 + }
995 + } elseif (!$is_subunit && $room['units'] == 1 && isset($room_bookings_pool[$room['id']][$prev_day_key][0])) {
996 + if (date('Y-m-d', $room_bookings_pool[$room['id']][$prev_day_key][0]['checkout']) == $cur_day_key) {
997 + // prepend checkout snake
998 + echo '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
999 + }
1000 + }
1001 + }
1002 + echo $critical_note;
1003 + ?>
1004 + </td>
1005 + <?php
1006 + }
1007 +
1008 + // iterate to next day
1009 + $moncurts = getdate(mktime(0, 0, 0, $moncurts['mon'], ($moncurts['mday'] + 1), $moncurts['year']));
1010 + }
1011 +
1012 + // room row parsed, check if we have to parse a sub-unit next
1013 + if (!$is_subunit && $room_bids_pool && isset($rooms_features_map[$room['id']])) {
1014 + /**
1015 + * Load bookings for distinctive features when parsing the parent $room array.
1016 + * Load also the eventually unassigned bookings.
1017 + *
1018 + * @since 1.18.2 (J) - 1.8.2 (WP)
1019 + * @since 1.18.3 (J) - 1.8.3 (WP) improved support for multi-room bookings with different stay dates.
1020 + */
1021 + list($room_indexes_bids, $room_unassigned_bids) = VikBooking::loadRoomIndexesBookings($room['id'], $room_bids_pool, $unassigned = true);
1022 + if ($room_indexes_bids || $room_unassigned_bids) {
1023 + // set room bookings pool values
1024 + $rooms_bids_pools[$room['id']] = $room_bids_pool;
1025 + }
1026 + if ($room_indexes_bids) {
1027 + // set bookings allocated per room index
1028 + $rooms_features_bookings[$room['id']] = $room_indexes_bids;
1029 + // build a list of room index positions for every booking ID
1030 + $bid_rindex_pos = [];
1031 + foreach ($room_indexes_bids as $rindex => $rindex_bids) {
1032 + foreach ((array) $rindex_bids as $seek_bid) {
1033 + $bid_rindex_pos[$seek_bid] = $bid_rindex_pos[$seek_bid] ?? [];
1034 + $bid_rindex_pos[$seek_bid][] = $rindex;
1035 + }
1036 + }
1037 + // map sub-unit room bookings
1038 + foreach ($room_indexes_bids as $rindex => $rindex_bids) {
1039 + foreach ((array) $rindex_bids as $seek_bid) {
1040 + // collect a list of busy IDs affecting the current room reservation record
1041 + $room_res_busy_ids = [];
1042 + foreach ($room_bookings as $day_ress) {
1043 + foreach ($day_ress as $day_res) {
1044 + if ($day_res['idorder'] == $seek_bid && !in_array($day_res['id'], $room_res_busy_ids)) {
1045 + // push busy ID
1046 + $room_res_busy_ids[] = $day_res['id'];
1047 + }
1048 + }
1049 + }
1050 + // sort the busy IDs affecting the current room reservation record
1051 + sort($room_res_busy_ids);
1052 + // iterate all room bookings
1053 + foreach ($room_bookings as $day_key => $day_ress) {
1054 + // filter all bookings by getting only the needed one(s)
1055 + $eligible_day_reservations = array_values(array_filter($day_ress, function($day_res) use ($seek_bid) {
1056 + // there could be multiple valid bookings in case of multi-room bookings
1057 + return $day_res['idorder'] == $seek_bid;
1058 + }));
1059 + // identify the position of this room index for the current booking ID
1060 + $rindex_res_busy_pos = array_search($rindex, $bid_rindex_pos[$seek_bid] ?? []);
1061 + // identify the busy ID that belongs to the current room index in case of multi-room bookings
1062 + $rindex_res_busy_id = $rindex_res_busy_pos !== false ? ($room_res_busy_ids[$rindex_res_busy_pos] ?? 0) : 0;
1063 + // filter all day reservations by getting only the one with the proper busy ID
1064 + $rindex_day_reservations = array_values(array_filter($eligible_day_reservations, function($res) use ($rindex_res_busy_id) {
1065 + return $rindex_res_busy_id && $res['id'] == $rindex_res_busy_id;
1066 + }));
1067 + if ($rindex_day_reservations[0] ?? []) {
1068 + // allocate the proper room-day-index booking even in case of multi-room bookings
1069 + $room_bookings_pool[$room['id']][$day_key][$rindex] = $rindex_day_reservations[0];
1070 + }
1071 + }
1072 + }
1073 + }
1074 + }
1075 + if ($room_unassigned_bids) {
1076 + // map room unassigned bookings with missing sub-unit (placeholder index = -1)
1077 + $use_rindex = -1;
1078 + // always set all bookings that are missing a sub-unit, not only the first booking record found
1079 + $rooms_features_bookings[$room['id']][$use_rindex] = $room_unassigned_bids;
1080 + // start unassigned bookings intersection container
1081 + $unassigned_bids_intersect = [];
1082 + // start counter
1083 + $unassigned_bids_count = 0;
1084 + // iterate the unassigned reservations
1085 + foreach ($room_unassigned_bids as $seek_bid) {
1086 + // scan all room bookings
1087 + foreach ($room_bookings as $day_key => $day_ress) {
1088 + foreach ($day_ress as $day_res) {
1089 + if ($day_res['idorder'] != $seek_bid || !empty($day_res['closure'])) {
1090 + // unwanted booking
1091 + continue;
1092 + }
1093 + if ($room_bookings_pool[$room['id']][$day_key][$use_rindex] ?? []) {
1094 + /**
1095 + * Even if there could be multiple room bookings without a sub-unit on the same room and day,
1096 + * we cannot allow to push more than booking, because the row for the missing sub-units is
1097 + * just one, and it is therefore impossible to display multiple reservations under the same day.
1098 + */
1099 + continue;
1100 + }
1101 + /**
1102 + * Make sure the booking with missing sub-unit that we are about to add is not intersecting other
1103 + * bookings for the same room that were previously pushed as missing sub-units. This technique
1104 + * allows us to push unlimited bookings with missing sub-units, rather than just the first one
1105 + * found in the current month (scroll=tables), or in the current period (scroll=inline), but we
1106 + * need to make sure the eventually multiple bookings added are not intersecting with each other,
1107 + * or the display of the bookings would be wrong with overlapping data on certain dates.
1108 + *
1109 + * @since 1.18.6 (J) - 1.8.6 (WP)
1110 + */
1111 + $is_intersecting_others = false;
1112 + foreach ($unassigned_bids_intersect as $previous_bid => $previous_intersection) {
1113 + if ($previous_bid == $day_res['idorder']) {
1114 + // this is fine, we need to keep pushing this booking for all stay dates
1115 + continue;
1116 + }
1117 + if (strtotime('00:00:00', $day_res['checkin']) <= $previous_intersection['checkout'] && strtotime('00:00:00', $day_res['checkout']) >= $previous_intersection['checkin']) {
1118 + // intersection found, this missing sub-unit room reservation would interfere
1119 + // with another reservation that was previously processed, so we skip it
1120 + $is_intersecting_others = true;
1121 + break;
1122 + }
1123 + }
1124 + if ($is_intersecting_others) {
1125 + // do not push this booking under the rooms booking pool for this day
1126 + // it can live in the rooms feature bookings array, but not in the pool
1127 + continue;
1128 + }
1129 + // start containers, if needed
1130 + if (!isset($room_bookings_pool[$room['id']])) {
1131 + $room_bookings_pool[$room['id']] = [];
1132 + }
1133 + if (!isset($room_bookings_pool[$room['id']][$day_key])) {
1134 + $room_bookings_pool[$room['id']][$day_key] = [];
1135 + }
1136 + // set room-day booking for the unassigned index
1137 + $room_bookings_pool[$room['id']][$day_key][$use_rindex] = $day_res;
1138 + // set room-day booking intersection data
1139 + $unassigned_bids_intersect[$day_res['idorder']] = [
1140 + 'checkin' => strtotime('00:00:00', $day_res['checkin']),
1141 + 'checkout' => strtotime('00:00:00', $day_res['checkout']),
1142 + ];
1143 + // increase counter
1144 + $unassigned_bids_count++;
1145 + }
1146 + }
1147 + }
1148 + if (!$unassigned_bids_count) {
1149 + // there were probably only closures, so we unset the bookings for the unassigned row index
1150 + $rooms_features_bookings[$room['id']][$use_rindex] = [];
1151 + }
1152 + }
1153 + }
1154 +
1155 + // close row
1156 + ?>
1157 + </tr>
1158 + <?php
1159 + }
1160 + ?>
1161 + </tbody>
1162 + </table>
1163 + </div>
1164 +</div>
1165 + <?php
1166 + // iterate to next month
1167 + $curts = getdate(mktime(0, 0, 0, ($nowts['mon'] + $mind), $nowts['mday'], $nowts['year']));
1168 +}
1169 +
1170 +//Prepare modal
1171 +?>
1172 +<script type="text/javascript">
1173 +var hasNewBooking = false;
1174 +var last_room_click = '',
1175 + last_date_click = '',
1176 + next_date_click = '';
1177 +function vboJModalHideCallback() {
1178 + if (hasNewBooking === true) {
1179 + location.reload();
1180 + }
1181 +}
1182 +function vboCallOpenJModal(identif, baseurl, options) {
1183 + options = typeof options !== 'object' ? {} : options;
1184 +
1185 + if (last_room_click && last_room_click.length) {
1186 + baseurl += '&cid[]='+last_room_click;
1187 + options.room_id = last_room_click;
1188 + last_room_click = '';
1189 + }
1190 + if (last_date_click && last_date_click.length) {
1191 + baseurl += '&checkin='+last_date_click;
1192 + options.checkin = last_date_click;
1193 + last_date_click = '';
1194 + }
1195 + if (next_date_click && next_date_click.length) {
1196 + baseurl += '&checkout='+next_date_click;
1197 + options.checkout = next_date_click;
1198 + next_date_click = '';
1199 + }
1200 +
1201 + // determine what type of modal to render
1202 + if (<?php echo VBOFactory::getConfig()->getBool('overv_newbook_legacy_modal', false) ? 'true' : 'false'; ?>) {
1203 + // use the ("legacy") modal from Bootstrap rendering the View calendar within an iFrame
1204 + vboOpenJModal(identif, baseurl);
1205 + } else {
1206 + // by default we rely on the VikBooking's native modal to render the admin-widget "bookings_calendar"
1207 + VBOCore.handleDisplayWidgetNotification({
1208 + widget_id: 'bookings_calendar',
1209 + }, {
1210 + id_room: options?.room_id || null,
1211 + offset: options?.checkin || null,
1212 + day: options?.checkin || null,
1213 + checkout: options?.checkout || null,
1214 + newbook: 1,
1215 + });
1216 + }
1217 +}
1218 +</script>
1219 +<?php
1220 +echo $vbo_app->getJmodalScript('', 'vboJModalHideCallback();', '');
1221 +echo $vbo_app->getJmodalHtml('vbo-new-res', JText::translate('VBOSHOWQUICKRES'));
1222 +//end Prepare modal
1223 +?>
1224 +<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>
1225 +<div class="vbo-info-overlay-block">
1226 + <div class="vbo-info-overlay-loading-dnd">
1227 + <span class="vbo-loading-dnd-head"></span>
1228 + <span class="vbo-loading-dnd-body"></span>
1229 + <span class="vbo-loading-dnd-footer"><?php echo JText::translate('VIKLOADING'); ?></span>
1230 + <span id="vbo-dnd-response" class="vbo-loading-dnd-response"></span>
1231 + <canvas id="vbo-dnd-canvas-success" height="250"></canvas>
1232 + </div>
1233 +</div>
1234 +
1235 +<form action="index.php?option=com_vikbooking" method="post" name="adminForm" id="adminForm">
1236 + <input type="hidden" name="option" value="com_vikbooking" />
1237 + <input type="hidden" name="task" value="overv" />
1238 + <input type="hidden" name="month" value="<?php echo $tsstart; ?>" />
1239 + <input type="hidden" name="mnum" value="<?php echo $mnum; ?>" />
1240 + <input type="hidden" name="category_id" value="<?php echo $pcategory_id; ?>" />
1241 + <?php echo '<br/>'.$navbut; ?>
1242 +</form>
1243 +
1244 +<a class="vbo-basenavuri-details" href="index.php?option=com_vikbooking&task=editorder&goto=overv&cid[]=%d" style="display: none;"></a>
1245 +<a class="vbo-basenavuri-edit" href="index.php?option=com_vikbooking&task=editbusy&goto=overv&cid[]=%d" style="display: none;"></a>
1246 +
1247 +<script type="text/Javascript">
1248 +const vboOvervMonthNames = <?php echo json_encode($month_names_cache ?? (new stdClass)); ?>;
1249 +
1250 +var hovtimer;
1251 +var hovtip = false;
1252 +var vbodialogorph_on = false;
1253 +var isdragging = false;
1254 +var debug_mode = '<?php echo $pdebug; ?>';
1255 +var bctags_count = <?php echo count($colortags); ?>;
1256 +var bctags_pool = <?php echo json_encode($colortags); ?>;
1257 +<?php
1258 +if ($colortags) {
1259 + $bctags_tip = '<div class=\"vbo-overview-tip-bctag-subtip-inner\">';
1260 + foreach ($colortags as $ctagk => $ctagv) {
1261 + $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>';
1262 + }
1263 + $bctags_tip .= '</div>';
1264 + ?>
1265 +var bctags_tip = "<?php echo $bctags_tip; ?>";
1266 + <?php
1267 +}
1268 +?>
1269 +</script>
1270 +
1271 +<script type="text/Javascript">
1272 +/**
1273 + * Render the units view mode
1274 + */
1275 +vboUnitsLeftOrBooked();
1276 +
1277 +/**
1278 + * Orphans dialog
1279 + */
1280 +function hideVboDialogOverv(action) {
1281 + if (vbodialogorph_on === true) {
1282 + jQuery(".vbo-orphans-overlay-block").fadeOut(400, function () {
1283 + jQuery(".vbo-info-overlay-content").show();
1284 + });
1285 + vbodialogorph_on = false;
1286 + }
1287 + // check action
1288 + if (action < 0) {
1289 + // stop reminding, set cookie
1290 + let nd = new Date();
1291 + nd.setTime(nd.getTime() + (365*24*60*60*1000));
1292 + document.cookie = "vboHideOrphans=1; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
1293 + }
1294 +}
1295 +
1296 +/* DnD global vars */
1297 +var cellspool = [];
1298 +var newcellspool = [];
1299 +
1300 +/* DnD Count the number of consecutive cells for this booking */
1301 +function countBookingCells(cellobj, bidstart, roomid) {
1302 + var totnights = 1;
1303 + var loop = true;
1304 + var cellelem = cellobj;
1305 + cellspool.push(cellelem);
1306 + while (loop === true) {
1307 + var next = cellelem.next('td');
1308 + if (next === undefined || !next.length) {
1309 + // attempt to go to the month after
1310 + var nextmonth = false;
1311 + var partable = cellelem.closest('.vbo-overv-montable-wrap').next('.vbo-overv-montable-wrap').find('table.vboverviewtable');
1312 + if (partable !== undefined && partable.length) {
1313 + partable.find('tr.vboverviewtablerow').each(function() {
1314 + var roomexists = jQuery(this).find('td').first();
1315 + if (roomexists !== undefined && roomexists.length) {
1316 + if (roomexists.attr('data-roomid') == roomid) {
1317 + nextmonth = true;
1318 + next = roomexists.next('td');
1319 + return true;
1320 + }
1321 + }
1322 + });
1323 + }
1324 + if (nextmonth === false) {
1325 + // nothing was found in the month after
1326 + loop = false;
1327 + break;
1328 + }
1329 + }
1330 + cellelem = next;
1331 + var nextbids = cellelem.attr('data-bids');
1332 + if (nextbids.length && nextbids.indexOf(bidstart) >= 0) {
1333 + cellspool.push(cellelem);
1334 + totnights++;
1335 + } else {
1336 + loop = false;
1337 + break;
1338 + }
1339 + }
1340 + return totnights;
1341 +}
1342 +
1343 +/* DnD Count the number of consecutive free date-cells for moving the booking onto the landing cell selected for drop */
1344 +function countCellFreeNights(landobj, roomid, totnights, moving_bids) {
1345 + var freenights = 1;
1346 + var loop = true;
1347 + var cellelem = landobj;
1348 +
1349 + // populate cells pool
1350 + newcellspool.push(cellelem);
1351 +
1352 + while (loop === true) {
1353 + var next = cellelem.next('td');
1354 + if (next === undefined || !next.length) {
1355 + // attempt to go to the month after
1356 + var nextmonth = false;
1357 + var partable = cellelem.closest('.vbo-overv-montable-wrap').next('.vbo-overv-montable-wrap').find('table.vboverviewtable');
1358 + if (partable !== undefined && partable.length) {
1359 + partable.find('tr.vboverviewtablerow').each(function() {
1360 + var roomexists = jQuery(this).find('td').first();
1361 + if (roomexists !== undefined && roomexists.length) {
1362 + if (roomexists.attr('data-roomid') == roomid) {
1363 + nextmonth = true;
1364 + next = roomexists.next('td');
1365 + return true;
1366 + }
1367 + }
1368 + });
1369 + }
1370 + if (nextmonth === false) {
1371 + // nothing was found in the month after
1372 + loop = false;
1373 + break;
1374 + }
1375 + }
1376 + cellelem = next;
1377 + var cell_bids = cellelem.attr('data-bids');
1378 + if (!cellelem.hasClass('busy') || (cellelem.hasClass('busy') && cellelem.hasClass('vbo-partially'))) {
1379 + // bookings of 1 night can stop here because there is availability for one day
1380 + if (parseInt(totnights) === 1) {
1381 + loop = false;
1382 + break;
1383 + }
1384 + // push free cell
1385 + newcellspool.push(cellelem);
1386 + freenights++;
1387 + if (freenights >= totnights) {
1388 + loop = false;
1389 + break;
1390 + }
1391 + } else {
1392 + // cell is occupied, check if it's occupied by the booking we are moving or not
1393 + if (!cell_bids || cell_bids != moving_bids) {
1394 + loop = false;
1395 + break;
1396 + }
1397 + // cell is occupied by the same booking we are moving, so this cell counts as free
1398 + newcellspool.push(cellelem);
1399 + freenights++;
1400 + if (freenights >= totnights) {
1401 + loop = false;
1402 + break;
1403 + }
1404 + }
1405 + }
1406 +
1407 + return freenights;
1408 +}
1409 +
1410 +/* DnD function to perform the ajax request of the booking modification */
1411 +function doAlterBooking(bid, roomid, landrid) {
1412 + var nowdatefrom = jQuery(newcellspool[0]).attr('data-day');
1413 + var nowdateto = jQuery(newcellspool[(newcellspool.length -1)]).attr('data-day');
1414 +
1415 + // perform the request
1416 + VBOCore.doAjax(
1417 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=alterbooking'); ?>",
1418 + {
1419 + tmpl: "component",
1420 + idorder: bid,
1421 + oldidroom: roomid,
1422 + idroom: landrid,
1423 + fromdate: nowdatefrom,
1424 + todate: nowdateto,
1425 + e4j_debug: debug_mode
1426 + },
1427 + (res) => {
1428 + if (res.indexOf('e4j.error') >= 0 ) {
1429 + alert(res.replace("e4j.error.", ""));
1430 + //restore the old cells
1431 + jQuery('td.vbo-dragging-cells-tmp').removeClass('vbo-dragging-cells-tmp');
1432 + jQuery('td.vbo-dragged-cells-tmp').removeClass('vbo-dragged-cells-tmp');
1433 + //
1434 + jQuery(".vbo-info-overlay-block").hide();
1435 + } else {
1436 + //move to the new cells and if there are already bookings on those dates, reload the page without moving the blocks
1437 + var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
1438 + var mustReload = obj_res.esit < 1 ? true : false;
1439 + //always force reload if booking was made for more than one room
1440 + var samebidcells = jQuery("td.vbo-checkinday[data-bids='"+bid+"']");
1441 + if (samebidcells.length > 1) {
1442 + mustReload = true;
1443 + }
1444 + //
1445 + jQuery(newcellspool).each(function(k, v) {
1446 + var cur_units_booked = parseInt(jQuery(cellspool[k]).find('a').parent('td').attr('data-units-booked'));
1447 + if ((v.hasClass('busy') && v.attr('data-bids') != jQuery(cellspool[k]).attr('data-bids')) || isNaN(cur_units_booked) || cur_units_booked > 1) {
1448 + mustReload = true;
1449 + return false;
1450 + }
1451 + });
1452 + if (mustReload !== true) {
1453 + /* switch cells */
1454 + var switchmap = [];
1455 + jQuery(cellspool).each(function(k, v) {
1456 + var switchcell = {
1457 + hcont: v.html(),
1458 + cl: v.attr('class'),
1459 + bids: v.attr('data-bids'),
1460 + tit: v.attr('title')
1461 + };
1462 + switchmap[k] = switchcell;
1463 + v.html("").attr('class', 'notbusy').attr('data-bids', '').attr('title', '');
1464 + });
1465 + jQuery(switchmap).each(function(k, v) {
1466 + jQuery(newcellspool[k]).html(v.hcont).attr('class', v.cl).attr('data-bids', v.bids).attr('title', v.tit);
1467 + // re-bind hover event
1468 + jQuery(newcellspool[k]).hover(function() {
1469 + registerHoveringTooltip(this);
1470 + }, unregisterHoveringTooltip);
1471 + //
1472 + });
1473 + jQuery('td.vbo-dragging-cells-tmp').removeClass('vbo-dragging-cells-tmp');
1474 + jQuery('td.vbo-dragged-cells-tmp').removeClass('vbo-dragged-cells-tmp');
1475 + }
1476 + if (obj_res.esit < 1) {
1477 + //some errors occurred after executing certain functions
1478 + if (obj_res.message.length) {
1479 + alert(obj_res.message);
1480 + }
1481 + document.location.href='<?php echo $file_base; ?>?option=com_vikbooking&task=overv';
1482 + } else {
1483 + finalizeDndUpdate(mustReload, obj_res);
1484 + }
1485 + }
1486 + },
1487 + (err) => {
1488 + alert("Request Failed");
1489 + // restore the old cells
1490 + jQuery('td.vbo-dragging-cells-tmp').removeClass('vbo-dragging-cells-tmp');
1491 + jQuery('td.vbo-dragged-cells-tmp').removeClass('vbo-dragged-cells-tmp');
1492 + //
1493 + jQuery(".vbo-info-overlay-block").hide();
1494 + }
1495 + );
1496 +}
1497 +
1498 +/* 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 */
1499 +function finalizeDndUpdate(mustReload, obj_res) {
1500 + jQuery('#vbo-dnd-response').html(obj_res.message+' '+obj_res.vcm);
1501 + jQuery('.vbo-loading-dnd-footer').hide();
1502 + var start = 100;
1503 + var mid = 145;
1504 + var end = 250;
1505 + var width = 22;
1506 + var leftX = start;
1507 + var leftY = start;
1508 + var rightX = mid - (width / 2.7);
1509 + var rightY = mid + (width / 2.7);
1510 + var animationSpeed = 20;
1511 + var closingdelay = 700;
1512 + var ctx = document.getElementById('vbo-dnd-canvas-success').getContext('2d');
1513 + ctx.lineWidth = width;
1514 + ctx.strokeStyle = 'rgba(0, 150, 0, 1)';
1515 + for (var i = start; i < mid; i++) {
1516 + var drawLeft = window.setTimeout(function () {
1517 + ctx.beginPath();
1518 + ctx.moveTo(start, start);
1519 + ctx.lineTo(leftX, leftY);
1520 + ctx.stroke();
1521 + leftX++;
1522 + leftY++;
1523 + }, 1 + (i * animationSpeed) / 3);
1524 + }
1525 + for (var i = mid; i < end; i++) {
1526 + var drawRight = window.setTimeout(function () {
1527 + ctx.beginPath();
1528 + ctx.moveTo(leftX, leftY);
1529 + ctx.lineTo(rightX, rightY);
1530 + ctx.stroke();
1531 + rightX++;
1532 + rightY--;
1533 + }, 1 + (i * animationSpeed) / 3);
1534 + }
1535 + //hide modal window
1536 + window.setTimeout(function () {
1537 + if (obj_res.vcm.length) {
1538 + 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>';
1539 + jQuery('#vbo-dnd-response').append(vcmbtn);
1540 + } else {
1541 + if (mustReload === true) {
1542 + document.location.href='<?php echo $file_base; ?>?option=com_vikbooking&task=overv';
1543 + } else {
1544 + jQuery('.vbo-info-overlay-block').fadeOut(400, function(){
1545 + //clear/reset canvas in case of previous drawing and response text
1546 + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1547 + jQuery('#vbo-dnd-response').html("");
1548 + //
1549 + });
1550 + }
1551 + }
1552 + }, closingdelay + (i * animationSpeed) / 3);
1553 + //
1554 +}
1555 +
1556 +/* DnD function that can be called by those with VCM that have disabled the automated updates. Simply closes the modal window */
1557 +function closeEsitDialog() {
1558 + jQuery('.vbo-info-overlay-block').fadeOut(400, function(){
1559 + //clear/reset canvas in case of previous drawing and response text
1560 + var ctx = document.getElementById('vbo-dnd-canvas-success').getContext('2d');
1561 + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1562 + jQuery('#vbo-dnd-response').html("");
1563 + //
1564 + });
1565 +}
1566 +
1567 +/* Hover Tooltip functions */
1568 +function registerHoveringTooltip(that) {
1569 + if (hovtip) {
1570 + return false;
1571 + }
1572 + if (hovtimer) {
1573 + clearTimeout(hovtimer);
1574 + hovtimer = null;
1575 + }
1576 + var elem = jQuery(that);
1577 + var cellheight = elem.outerHeight();
1578 + var celldata = new Array();
1579 + if (elem.hasClass('subroom-busy')) {
1580 + celldata.push(elem.parent('tr').attr('data-subroomid'));
1581 + celldata.push(elem.attr('data-day'));
1582 + }
1583 + hovtimer = setTimeout(() => {
1584 + if (isdragging || (vboActionRoomRateData && vboActionRoomRateData?.start)) {
1585 + // prevent tooltip from popping up if dragging or if selecting room rates
1586 + unregisterHoveringTooltip();
1587 +
1588 + return;
1589 + }
1590 +
1591 + // turn flag on
1592 + hovtip = true;
1593 +
1594 + // calculate cell-element position
1595 + let pos_top = elem.offset().top;
1596 + let pos_left = elem.offset().left;
1597 + let elem_height = elem.outerHeight();
1598 + let screen_width = window?.screen?.width || 0;
1599 +
1600 + // build tooltip block element
1601 + let tooltip_block = jQuery('<div></div>');
1602 + tooltip_block.addClass('vbo-overview-tipblock');
1603 + tooltip_block.append("<div class=\"vbo-overview-tipinner\"><span class=\"vbo-overview-tiploading\">" + Joomla.JText._('VIKLOADING') + "</span></div>");
1604 + 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>");
1605 +
1606 + // calculate block position
1607 + tooltip_block.css('top', (pos_top + elem_height - 16) + 'px');
1608 + if (screen_width > 600 && (pos_left + 400) > screen_width) {
1609 + // place the tooltip starting from right
1610 + tooltip_block.css('left', (pos_left - (400 - elem.outerWidth())) + 'px');
1611 + } else {
1612 + // regular placing starting from left
1613 + tooltip_block.css('left', (pos_left - 6) + 'px');
1614 + }
1615 +
1616 + // append block to body
1617 + tooltip_block.appendTo(jQuery('body'));
1618 +
1619 + // load tooltip bookings
1620 + loadTooltipBookings(elem.attr('data-bids'), celldata);
1621 + }, 1500);
1622 +}
1623 +
1624 +function unregisterHoveringTooltip() {
1625 + clearTimeout(hovtimer);
1626 + hovtimer = null;
1627 +}
1628 +
1629 +/**
1630 + * @deprecated 1.16.10 (J) - 1.6.10 (WP)
1631 + */
1632 +function adjustHoveringTooltip() {
1633 + setTimeout(() => {
1634 + var ver_difflim = 35;
1635 + var hor_difflim = 20;
1636 + var tip_block = jQuery('.vbo-overview-tipblock');
1637 + if (!tip_block || !tip_block.length) {
1638 + return;
1639 + }
1640 + var table_wrap = tip_block.closest('.vbo-overv-montable-wrap');
1641 +
1642 + if (tip_block.outerHeight() > table_wrap.outerHeight()) {
1643 + // tooltip is too tall to fit in the table, render the modal instead
1644 + tip_block.hide().closest('td.busy').trigger('click');
1645 + return;
1646 + }
1647 +
1648 + // vertical positioning
1649 + var otop = tip_block.offset().top;
1650 + if (otop > 0 && otop < ver_difflim) {
1651 + // adjust tooltip position
1652 + tip_block.css('bottom', '-=' + (ver_difflim - otop));
1653 + } else if (otop < table_wrap.offset().top) {
1654 + // tooltip exceeds wrapping table, move it underneath the cell
1655 + var extra_padding_top = 0;
1656 + var tip_inner_padtop = tip_block.find('.vbo-overview-tipinner').css('padding-top');
1657 + if (tip_inner_padtop) {
1658 + // get only numbers
1659 + extra_padding_top = tip_inner_padtop.replace(/[^0-9]/g, '');
1660 + extra_padding_top = !isNaN(extra_padding_top) ? parseInt(extra_padding_top) : 0;
1661 + }
1662 + tip_block.css('bottom', 'auto').css('top', (tip_block.parent('td').outerHeight() - extra_padding_top));
1663 + }
1664 +
1665 + // horizontal positioning
1666 + if (tip_block.offset().left < table_wrap.offset().left) {
1667 + var left_diff = table_wrap.offset().left - tip_block.offset().left;
1668 + if (left_diff > hor_difflim) {
1669 + // tooltip exceeds table on the left, move it to the right of the cell (i.e 1st day of month)
1670 + tip_block.css('right', tip_block.css('left'));
1671 + tip_block.css('left', 'unset');
1672 + }
1673 + }
1674 + }, 60);
1675 +}
1676 +
1677 +function hideVboTooltip() {
1678 + jQuery('.vbo-overview-tipblock').remove();
1679 + hovtip = false;
1680 +}
1681 +
1682 +function loadTooltipBookings(bids, celldata) {
1683 + if (!bids || bids === undefined || !bids.length) {
1684 + hideVboTooltip();
1685 + return false;
1686 + }
1687 +
1688 + var subroomdata = celldata.length ? celldata[0] : '';
1689 +
1690 + // perform the request
1691 + VBOCore.doAjax(
1692 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=getbookingsinfo'); ?>",
1693 + {
1694 + tmpl: "component",
1695 + idorders: bids,
1696 + subroom: subroomdata
1697 + },
1698 + (res) => {
1699 + try {
1700 + var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
1701 + jQuery('.vbo-overview-tiploading').remove();
1702 + var container = jQuery('.vbo-overview-tipinner');
1703 + jQuery(obj_res).each(function(k, v) {
1704 + // get base navigation URIs
1705 + var base_uri_details = jQuery('.vbo-basenavuri-details').attr('href');
1706 + var base_uri_edit = jQuery('.vbo-basenavuri-edit').attr('href');
1707 +
1708 + // build content
1709 + var bcont = "<div class=\"vbo-overview-tip-bookingcont\">";
1710 + bcont += "<div class=\"vbo-overview-tip-bookingcont-left\">";
1711 + 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>";
1712 + 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>";
1713 + 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>";
1714 + if (bctags_count > 0) {
1715 + var bctag_title = '';
1716 + var bctag_color = '#ffffff';
1717 + if (v.colortag.hasOwnProperty('color')) {
1718 + bctag_color = v.colortag.color;
1719 + bctag_title = v.colortag.name;
1720 + }
1721 + 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>";
1722 + }
1723 + bcont += "</div>";
1724 + bcont += "<div class=\"vbo-overview-tip-bookingcont-right\">";
1725 + 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>";
1726 + if (v.roomsnum > 1) {
1727 + 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>";
1728 + }
1729 + 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>";
1730 + if (v.hasOwnProperty('rindexes')) {
1731 + for (var rindexk in v.rindexes) {
1732 + if (v.rindexes.hasOwnProperty(rindexk)) {
1733 + 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>";
1734 + }
1735 + }
1736 + }
1737 + if (v.hasOwnProperty('channelimg')) {
1738 + 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>";
1739 + }
1740 + if (v.hasOwnProperty('optindexes') && celldata.length) {
1741 + var subroomids = celldata[0].split('-');
1742 + 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>";
1743 + }
1744 + bcont += "<div class=\"vbo-overview-tip-bookingcont-total\">";
1745 + bcont += "<div class=\"vbo-overview-tip-btot\"><span class=\"vbo-overview-tip-lbl\">" + Joomla.JText._('VBEDITORDERNINE') + "</span><span class=\"vbo-overview-tip-cnt\">" + VBOCore.getCurrency().display(v.format_tot) + "</span></div>";
1746 + if (v.totpaid > 0.00) {
1747 + 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\">" + VBOCore.getCurrency().display(v.format_totpaid) + "</span></div>";
1748 + }
1749 + var getnotes = v.adminnotes;
1750 + if (getnotes !== null && getnotes.length) {
1751 + 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>";
1752 + }
1753 + bcont += "</div>";
1754 + bcont += "</div>";
1755 + bcont += "</div>";
1756 + container.append(bcont);
1757 + jQuery('.vbo-overview-tipexpander').show();
1758 + });
1759 +
1760 + /**
1761 + * @deprecated 1.16.10 (J) - 1.6.10 (WP)
1762 + */
1763 + // adjust the position so that it won't go under other contents
1764 + // adjustHoveringTooltip();
1765 +
1766 + jQuery(".hasTooltip").tooltip();
1767 + } catch(err) {
1768 + // restore
1769 + hideVboTooltip();
1770 + // display error
1771 + console.error('could not parse JSON response', err, res);
1772 + alert('Could not parse JSON response');
1773 + }
1774 + },
1775 + (err) => {
1776 + // restore
1777 + hideVboTooltip();
1778 + // display error
1779 + console.error(err);
1780 + alert(err.responseText);
1781 + }
1782 + );
1783 +}
1784 +
1785 +/**
1786 + * Set a sub-unit to a room booking. Triggered by the room-day bookings modal
1787 + */
1788 +function vboSetSubunit(bid, rid, orkey, rindex) {
1789 + if (!(rindex + '').length) {
1790 + return false;
1791 + }
1792 +
1793 + if (!confirm(Joomla.JText._('VBOFEATASSIGNUNIT') + rindex + '?')) {
1794 + return false;
1795 + }
1796 +
1797 + // show loading
1798 + VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
1799 +
1800 + // make the request to set the room sub-unit
1801 + VBOCore.doAjax(
1802 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.set_room_booking_subunit'); ?>",
1803 + {
1804 + bid: bid,
1805 + rid: rid,
1806 + orkey: orkey,
1807 + rindex: rindex,
1808 + tmpl: 'component'
1809 + },
1810 + (res) => {
1811 + // dismiss the modal (no need to stop the loading)
1812 + VBOCore.emitEvent('vbo-dismiss-modal-overv-rdaybookings');
1813 +
1814 + try {
1815 + var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
1816 + if (!obj_res.hasOwnProperty('nights') || !Array.isArray(obj_res['nights']) || !obj_res['nights'].length) {
1817 + // invalid response
1818 + throw new Error('Invalid response');
1819 + }
1820 +
1821 + // check if cells can be occupied
1822 + var sub_rows = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + rindex + '"]');
1823 + if (!sub_rows || !sub_rows.length) {
1824 + console.error('Could not find any room sub-unit row');
1825 + return false;
1826 + }
1827 +
1828 + // loop through all sub-unit rows to match the cells for the nights affected
1829 + sub_rows.each(function() {
1830 + var sub_row = jQuery(this);
1831 + obj_res['nights'].forEach((ymd, kindex) => {
1832 + let sub_row_cell = sub_row.find('td[data-day="' + ymd + '"]');
1833 + if (sub_row_cell.length) {
1834 + sub_row_cell.removeClass('subnotbusy').addClass('subroom-busy');
1835 + if (kindex == 0) {
1836 + sub_row_cell.addClass('vbo-checkinday');
1837 + }
1838 + sub_row_cell.attr('data-bids', '-' + obj_res['bid'] + '-');
1839 + if (!sub_row_cell.find('span.vbo-draggable-sp').length) {
1840 + sub_row_cell.prepend('<span class="vbo-draggable-sp"><a class="vbo-overview-subredday">&bull;</a></span>');
1841 + }
1842 + }
1843 + });
1844 + });
1845 + } catch(err) {
1846 + console.error('could not parse JSON response', err, res);
1847 + alert('Could not parse JSON response');
1848 + }
1849 + },
1850 + (err) => {
1851 + // stop loading
1852 + VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
1853 +
1854 + // log and display the error
1855 + console.error(err);
1856 + alert(err.responseText);
1857 + }
1858 + );
1859 +}
1860 +
1861 +/**
1862 + * Move a subunit group of cells to another subroom row. Triggered by
1863 + * the hovering tooltip, by the room-day bookings modal, or by DnD etc..
1864 + */
1865 +function vboMoveSubunit(bid, rid, old_rindex, new_rindex, dday) {
1866 + let new_rindex_name = new_rindex.replace('#', '');
1867 + let is_tmp_subunit_row = new_rindex_name == 0;
1868 + let is_from_tmp_row = old_rindex == 0 || old_rindex == -1;
1869 +
1870 + if (new_rindex_name == -1 || (old_rindex == -1 && is_tmp_subunit_row)) {
1871 + // no landing on the unassigned row, or unassigned room cannot land on the holding area
1872 + alert(<?php echo json_encode(JText::translate('VBO_UNASSIGNED_ROW_FORBID')); ?>);
1873 + return false;
1874 + }
1875 +
1876 + // request confirmation, if needed
1877 + if (!is_tmp_subunit_row && !confirm(Joomla.JText._('VBOFEATASSIGNUNIT') + new_rindex_name + '?')) {
1878 + return false;
1879 + }
1880 +
1881 + // check if movement can be made
1882 + var cur_tr = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + old_rindex + '"]');
1883 + if (!cur_tr || !cur_tr.length) {
1884 + console.error('could not find the parent row of the subunit cells');
1885 + return false;
1886 + }
1887 + var maincell = cur_tr.find('td.subroom-busy[data-day="' + dday + '"]');
1888 + if (!maincell || !maincell.length) {
1889 + console.error('could not find the main cell of the subunit to move');
1890 + return false;
1891 + }
1892 + var dest_tr = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + new_rindex + '"]');
1893 + if (!dest_tr || !dest_tr.length) {
1894 + console.error('could not find the destination row for the subunit cells');
1895 + return false;
1896 + }
1897 + var targetbids = maincell.attr('data-bids');
1898 + if (targetbids.indexOf('-' + bid + '-') < 0) {
1899 + console.error('given bid does not match with cell bids', bid, targetbids);
1900 + return false;
1901 + }
1902 + var firstcell = maincell;
1903 + var loop = true;
1904 + while (loop === true) {
1905 + var prevsib = firstcell.prev();
1906 + if (prevsib && prevsib.length && prevsib.attr('data-bids') && prevsib.attr('data-bids').indexOf('-' + bid + '-') >= 0) {
1907 + firstcell = prevsib;
1908 + } else {
1909 + loop = false;
1910 + }
1911 + }
1912 + // make sure to get the check-in day (first cell)
1913 + dday = firstcell.attr('data-day');
1914 + var destcell = dest_tr.find('td.subnotbusy[data-day="' + dday + '"]');
1915 + if (!destcell || !destcell.length) {
1916 + // first destination cell is occupied, check if swap is allowed
1917 + if (dest_tr.find('td.subroom-busy[data-day="' + dday + '"]').length) {
1918 + var landbids = dest_tr.find('td.subroom-busy[data-day="' + dday + '"]').attr('data-bids');
1919 + return vboSwapRoomSubunits(bid, landbids.replaceAll('-', ''), rid, old_rindex, new_rindex, dday);
1920 + }
1921 + console.error('could not find the first free destination cell');
1922 + return false;
1923 + }
1924 + // check if all dates are free before making movements. Redundant, but useful.
1925 + var freedates = true;
1926 + var copyfirstcell = firstcell;
1927 + var copydday = dday;
1928 + loop = true;
1929 + while (loop === true) {
1930 + var nextsib = copyfirstcell.next();
1931 + if (nextsib && nextsib.length && nextsib.attr('data-bids') && nextsib.attr('data-bids').indexOf('-' + bid + '-') >= 0) {
1932 + copyfirstcell = nextsib;
1933 + copydday = copyfirstcell.attr('data-day');
1934 + var copydestcell = dest_tr.find('td.subnotbusy[data-day="' + copydday + '"]');
1935 + if (!copydestcell || !copydestcell.length) {
1936 + console.error('could not find the next free destination cell for ' + copydday);
1937 + alert(<?php echo json_encode(JText::translate('VBBOOKNOTMADE')); ?>);
1938 + freedates = false;
1939 + loop = false;
1940 + }
1941 + } else {
1942 + loop = false;
1943 + }
1944 + }
1945 + if (freedates === false) {
1946 + return false;
1947 + }
1948 +
1949 + // loading opacity
1950 + jQuery('.vbo-overview-tipblock').css('opacity', '0.6');
1951 +
1952 + // perform the request
1953 + VBOCore.doAjax(
1954 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=switchRoomIndex'); ?>",
1955 + {
1956 + tmpl: "component",
1957 + bid: bid,
1958 + rid: rid,
1959 + old_rindex: old_rindex,
1960 + new_rindex: new_rindex,
1961 + is_tmp_row: is_tmp_subunit_row ? 1 : 0,
1962 + is_from_tmp_row: is_from_tmp_row ? 1 : 0,
1963 + },
1964 + (res) => {
1965 + if (res.indexOf('e4j.error') >= 0 ) {
1966 + alert(res.replace("e4j.error.", ""));
1967 +
1968 + // restore loading opacity in container
1969 + jQuery('.vbo-overview-tipblock').css('opacity', '1');
1970 +
1971 + // abort
1972 + return;
1973 + }
1974 +
1975 + // hide tooltip
1976 + hideVboTooltip();
1977 +
1978 + // loop for moving cells
1979 + loop = true;
1980 + while (loop === true) {
1981 + // populate destination cell attributes, classes and content
1982 + destcell.removeClass('subnotbusy').addClass('subroom-busy').attr('data-bids', firstcell.attr('data-bids'));
1983 + if (firstcell.hasClass('vbo-checkinday')) {
1984 + firstcell.removeClass('vbo-checkinday');
1985 + destcell.addClass('vbo-checkinday');
1986 + }
1987 + // populate inline styling
1988 + let orig_style = firstcell.attr('style');
1989 + destcell.attr('style', orig_style);
1990 + firstcell.attr('style', '');
1991 + // populate content
1992 + firstcell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').appendTo(destcell);
1993 +
1994 + // re-bind hover event for tooltip
1995 + jQuery(destcell).hover(function() {
1996 + registerHoveringTooltip(this);
1997 + }, unregisterHoveringTooltip);
1998 +
1999 + // clean up the previous cell from which we moved data
2000 + firstcell.removeClass('subroom-busy').addClass('subnotbusy').attr('data-bids', '');
2001 +
2002 + // check if we have a next iteration to perform
2003 + let nextsib = firstcell.next();
2004 + if (nextsib && nextsib.length && nextsib.attr('data-bids') && nextsib.attr('data-bids').indexOf('-' + bid + '-') >= 0) {
2005 + // prepare data for the next iteration
2006 + firstcell = nextsib;
2007 + dday = firstcell.attr('data-day');
2008 + destcell = dest_tr.find('td.subnotbusy[data-day="' + dday + '"]');
2009 + if (!destcell || !destcell.length) {
2010 + console.error('could not find the next free destination cell');
2011 + loop = false;
2012 + }
2013 + } else {
2014 + // all cells were moved, break the loop
2015 + loop = false;
2016 + // check if we need to move the checkout snake as well
2017 + if (nextsib && nextsib.find('.vbo-tableaux-booking-checkout').length) {
2018 + dday = nextsib.attr('data-day');
2019 + destcell = dest_tr.find('td.subnotbusy[data-day="' + dday + '"]');
2020 + if (!destcell || !destcell.length) {
2021 + // the landing checkout snake may be on an occupied date
2022 + destcell = dest_tr.find('td.subroom-busy[data-day="' + dday + '"]');
2023 + }
2024 + if (destcell && destcell.length) {
2025 + // prepend the checkout snake to the next cell
2026 + nextsib.find('.vbo-tableaux-booking-checkout').prependTo(destcell);
2027 + }
2028 + }
2029 + }
2030 + }
2031 +
2032 + // dispatch the event for the sub-units updated
2033 + VBOCore.emitEvent('vbo-overv-sub-units-updated');
2034 + },
2035 + (err) => {
2036 + alert('Request Failed');
2037 + // restore loading opacity in container
2038 + jQuery('.vbo-overview-tipblock').css('opacity', '1');
2039 + }
2040 + );
2041 +
2042 + return true;
2043 +}
2044 +
2045 +/**
2046 + * Swap room sub-unit with another sub-unit on an occupied date. Triggered by DnD or by manual sub-unit switching.
2047 + */
2048 +function vboSwapRoomSubunits(bid, bidtwo, rid, old_rindex, new_rindex, dday) {
2049 + if (old_rindex == 0 || new_rindex == 0) {
2050 + // prevent swapping with a room reservation in the holding area (temporary sub-units row)
2051 + alert(<?php echo json_encode(JText::translate('VBO_HOLDING_AREA_NO_SWAP')); ?>);
2052 + return false;
2053 + }
2054 + if (old_rindex == -1 || new_rindex == -1) {
2055 + // prevent swapping with a room reservation in the unassigned row
2056 + alert(<?php echo json_encode(JText::translate('VBO_UNASSIGNED_ROW_FORBID')); ?>);
2057 + return false;
2058 + }
2059 + if (!confirm(Joomla.JText._('VBO_CONF_SWAP_RNUMB').replace('%s', old_rindex).replace('%s', new_rindex))) {
2060 + return false;
2061 + }
2062 + if (!bid || !bidtwo || !rid || !old_rindex || !new_rindex || !dday) {
2063 + console.error('Missing required arguments', bid, bidtwo, rid, old_rindex, new_rindex, dday);
2064 + return false;
2065 + }
2066 + // check if movement can be made
2067 + let cur_tr = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + old_rindex + '"]');
2068 + if (!cur_tr || !cur_tr.length) {
2069 + console.error('could not find the parent row of the subunit cells');
2070 + return false;
2071 + }
2072 + let maincell = cur_tr.find('td.subroom-busy[data-day="' + dday + '"]');
2073 + if (!maincell || !maincell.length) {
2074 + console.error('could not find the main cell of the subunit to move');
2075 + return false;
2076 + }
2077 + let dest_tr = jQuery('tr.vboverviewtablerow-subunit[data-subroomid="' + rid + '-' + new_rindex + '"]');
2078 + if (!dest_tr || !dest_tr.length) {
2079 + console.error('could not find the destination row for the subunit cells');
2080 + return false;
2081 + }
2082 + let destcell = dest_tr.find('td.subroom-busy[data-day="' + dday + '"]');
2083 + if (!destcell || !destcell.length) {
2084 + console.error('could not find the destination cell of the subunit to move');
2085 + return false;
2086 + }
2087 +
2088 + // gather the cells for the swop
2089 + let swap_cells_one = [];
2090 + let swap_cells_two = [];
2091 + let swap_dates_one = [];
2092 + let swap_dates_two = [];
2093 + let swap_styles_one = [];
2094 + let swap_styles_two = [];
2095 + let swap_cell_from = maincell;
2096 + let swap_cell_to = destcell;
2097 +
2098 + // register the elements to swap of the first sub-unit
2099 + if (!swap_cell_from.hasClass('vbo-checkinday')) {
2100 + // attempt to select the first cell of this booking in the current month
2101 + while (true) {
2102 + let prev_cell_from = swap_cell_from.prev();
2103 + if (!prev_cell_from || !prev_cell_from.length) {
2104 + break;
2105 + }
2106 + let prev_bids = prev_cell_from.attr('data-bids');
2107 + if (!prev_bids || prev_bids.indexOf('-' + bid + '-') < 0) {
2108 + break;
2109 + }
2110 + swap_cell_from = prev_cell_from;
2111 + }
2112 + }
2113 + while (true) {
2114 + if (!swap_cell_from || !swap_cell_from.length) {
2115 + // next cell not found
2116 + break;
2117 + }
2118 + let swap_bids = swap_cell_from.attr('data-bids');
2119 + if (!swap_bids || swap_bids.indexOf('-' + bid + '-') < 0) {
2120 + // last occupied cell reached
2121 + break;
2122 + }
2123 + // push cloned cell data to move
2124 + swap_cells_one.push(swap_cell_from.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').clone(true));
2125 + swap_dates_one.push(swap_cell_from.attr('data-day'));
2126 + swap_styles_one.push(swap_cell_from.attr('style'));
2127 + // set next iteration cell
2128 + swap_cell_from = swap_cell_from.next();
2129 + }
2130 +
2131 + // register the elements to swap of the second sub-unit
2132 + while (true) {
2133 + if (!swap_cell_to || !swap_cell_to.length) {
2134 + // next cell not found
2135 + break;
2136 + }
2137 + let swap_bids = swap_cell_to.attr('data-bids');
2138 + if (!swap_bids || swap_bids.indexOf('-' + bidtwo + '-') < 0) {
2139 + // last occupied cell reached
2140 + break;
2141 + }
2142 + // 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
2143 + if (!swap_cells_two.length && !swap_cell_to.hasClass('vbo-checkinday')) {
2144 + // prepend first the cells until the check-in or until not found
2145 + let prev_cell = swap_cell_to;
2146 + while (true) {
2147 + prev_cell = prev_cell.prev();
2148 + if (!prev_cell || !prev_cell.length) {
2149 + break;
2150 + }
2151 + let prev_cell_snake = prev_cell.find('.vbo-tableaux-booking').not('.vbo-tableaux-booking-checkout');
2152 + if (prev_cell_snake && prev_cell_snake.length && (prev_cell_snake.hasClass('vbo-tableaux-booking-checkin') || prev_cell_snake.hasClass('vbo-tableaux-booking-stay'))) {
2153 + // prepend cloned cell data to move
2154 + swap_cells_two.unshift(prev_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').clone(true));
2155 + swap_dates_two.unshift(prev_cell.attr('data-day'));
2156 + swap_styles_two.unshift(prev_cell.attr('style'));
2157 + } else {
2158 + // no more previous cells
2159 + break;
2160 + }
2161 + if (prev_cell.hasClass('vbo-checkinday')) {
2162 + break;
2163 + }
2164 + }
2165 + }
2166 + // push cloned cell data to move
2167 + swap_cells_two.push(swap_cell_to.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').clone(true));
2168 + swap_dates_two.push(swap_cell_to.attr('data-day'));
2169 + swap_styles_two.push(swap_cell_to.attr('style'));
2170 + // set next iteration cell
2171 + swap_cell_to = swap_cell_to.next();
2172 + }
2173 +
2174 + if (!swap_cells_one.length || !swap_cells_two.length) {
2175 + console.error('could not gather the cells to swap', swap_cells_one, swap_cells_two);
2176 + return false;
2177 + }
2178 +
2179 + // walk over the cells of the first room to swap and check the corresponding destination
2180 + let check_destcell = destcell;
2181 + for (let i = 0; i < swap_cells_one.length; i++) {
2182 + if (!check_destcell || !check_destcell.length) {
2183 + // this cell does not exist, abort
2184 + console.error('destination cell to #' + (i + 1) + ' was not found');
2185 + return false;
2186 + }
2187 + let dest_bids = check_destcell.attr('data-bids');
2188 + if (dest_bids && dest_bids.length && dest_bids.indexOf('-' + bidtwo + '-') < 0) {
2189 + // this cell is already occupied, abort
2190 + console.error('destination cell to #' + (i + 1) + ' is occupied by another reservation');
2191 + alert(<?php echo json_encode(JText::translate('VBBOOKNOTMADE')); ?>);
2192 + return false;
2193 + }
2194 + // set next destination cell
2195 + check_destcell = check_destcell.next();
2196 + }
2197 +
2198 + // walk over the cells of the second room to swap and check the corresponding destination
2199 + check_destcell = maincell;
2200 + for (let i = 0; i < swap_cells_two.length; i++) {
2201 + if (!check_destcell || !check_destcell.length) {
2202 + // this cell does not exist, abort
2203 + console.error('destination cell from #' + (i + 1) + ' was not found');
2204 + return false;
2205 + }
2206 + let dest_bids = check_destcell.attr('data-bids');
2207 + if (dest_bids && dest_bids.length && dest_bids.indexOf('-' + bid + '-') < 0) {
2208 + // this cell is already occupied, abort
2209 + console.error('destination cell from #' + (i + 1) + ' is occupied by another reservation');
2210 + alert(<?php echo json_encode(JText::translate('VBBOOKNOTMADE')); ?>);
2211 + return false;
2212 + }
2213 + // set next destination cell
2214 + check_destcell = check_destcell.next();
2215 + }
2216 +
2217 + // make the AJAX request and swap cells only in case of success
2218 + VBOCore.doAjax(
2219 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.swap_room_subunits'); ?>",
2220 + {
2221 + bid_one: bid,
2222 + bid_two: bidtwo,
2223 + rid: rid,
2224 + index_one: old_rindex,
2225 + index_two: new_rindex,
2226 + checkin: dday,
2227 + tmpl: 'component'
2228 + },
2229 + (res) => {
2230 + // init values from selection
2231 + let target_cell = destcell;
2232 + let current_cell = maincell;
2233 + let different_lengths = swap_dates_one.length != swap_dates_two.length;
2234 + let swap_diff_dates = false;
2235 +
2236 + // move the cells of the first sub-unit onto the cells of the second sub-unit
2237 + for (let counter = 0; counter < swap_cells_one.length; counter++) {
2238 + // remove elements from target
2239 + target_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
2240 + // swap cell elements
2241 + swap_cells_one[counter].appendTo(target_cell);
2242 + // set proper bids attribute
2243 + target_cell.attr('data-bids', '-' + bid + '-');
2244 + // restore initial cell styling
2245 + if ((swap_styles_one[counter] || 0)) {
2246 + target_cell.attr('style', swap_styles_one[counter]);
2247 + }
2248 + if (swap_dates_one.length > swap_dates_two.length) {
2249 + if (!(swap_dates_two[counter] || 0) || swap_dates_two.length == counter + 1) {
2250 + current_cell.attr('style', '');
2251 + }
2252 + }
2253 + // check if we are swapping with a non check-in day sub-unit occupied cell
2254 + if (counter === 0 && !target_cell.hasClass('vbo-checkinday')) {
2255 + target_cell.addClass('vbo-checkinday');
2256 + swap_diff_dates = true;
2257 + } else if (counter > 0 && !target_cell.hasClass('subroom-busy')) {
2258 + target_cell.removeClass('subnotbusy').addClass('subroom-busy');
2259 + }
2260 + // check if the reservations have different nights of stay
2261 + let cell_will_occupy = true;
2262 + if (counter > 0 && swap_diff_dates && !swap_dates_two.includes(swap_dates_one[counter])) {
2263 + // the swapped unit will not occupy this cell, so clean it
2264 + cell_will_occupy = false;
2265 + } else if ((counter + 1) > swap_cells_two.length) {
2266 + // this reservation is longer than the other
2267 + cell_will_occupy = false;
2268 + }
2269 + if (!cell_will_occupy) {
2270 + // remove the children elements from this longer reservation
2271 + current_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
2272 + // remove the occupied class as well
2273 + current_cell.removeClass('subroom-busy').addClass('subnotbusy').attr('data-bids', '');
2274 + }
2275 + // set next target cell
2276 + target_cell = target_cell.next();
2277 + // update current cell
2278 + current_cell = current_cell.next();
2279 + }
2280 +
2281 + // move the checkout snake of the first sub-unit
2282 + if (target_cell && target_cell.length && current_cell && current_cell.length && current_cell.find('.vbo-tableaux-booking-checkout').length) {
2283 + current_cell.find('.vbo-tableaux-booking-checkout').prependTo(target_cell);
2284 + }
2285 +
2286 + // move the cells of the second sub-unit onto the cells of the first sub-unit
2287 + target_cell = maincell;
2288 + current_cell = destcell;
2289 +
2290 + // check if we are swapping with a non check-in day sub-unit occupied cell
2291 + if (swap_diff_dates) {
2292 + let prev_land_cell = current_cell;
2293 + while (true) {
2294 + prev_land_cell = prev_land_cell.prev();
2295 + if (!prev_land_cell || !prev_land_cell.length) {
2296 + break;
2297 + }
2298 + if (prev_land_cell.hasClass('subroom-busy')) {
2299 + // overwrite first cell to move and to land
2300 + current_cell = prev_land_cell;
2301 + target_cell = target_cell.prev();
2302 + // empty this cell because no one will occupy it
2303 + current_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
2304 + // make the cell free
2305 + current_cell.removeClass('subroom-busy').addClass('subnotbusy').attr('data-bids', '');
2306 + }
2307 + if (prev_land_cell.hasClass('vbo-checkinday')) {
2308 + // we have reached the first cell of this booking
2309 + break;
2310 + }
2311 + }
2312 + }
2313 +
2314 + for (let counter = 0; counter < swap_cells_two.length; counter++) {
2315 + // remove elements from target
2316 + target_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
2317 + // swap cell elements
2318 + swap_cells_two[counter].appendTo(target_cell);
2319 + // set proper bids attribute
2320 + target_cell.attr('data-bids', '-' + bidtwo + '-');
2321 + // restore initial cell styling
2322 + if ((swap_styles_two[counter] || 0)) {
2323 + target_cell.attr('style', swap_styles_two[counter]);
2324 + }
2325 + if (!(swap_dates_one[counter] || 0) && swap_dates_one.length > swap_dates_two.length) {
2326 + current_cell.attr('style', '');
2327 + }
2328 + // check if we are swapping with a non check-in day sub-unit occupied cell
2329 + if (counter === 0 && !target_cell.hasClass('vbo-checkinday')) {
2330 + target_cell.addClass('vbo-checkinday');
2331 + if (current_cell.hasClass('vbo-checkinday') && !swap_dates_one.includes(swap_dates_two[counter])) {
2332 + current_cell.removeClass('vbo-checkinday');
2333 + }
2334 + } else if (counter > 0 && target_cell.hasClass('vbo-checkinday')) {
2335 + target_cell.removeClass('vbo-checkinday');
2336 + }
2337 + if (!target_cell.hasClass('subroom-busy')) {
2338 + target_cell.removeClass('subnotbusy').addClass('subroom-busy');
2339 + }
2340 + // check if the reservations have different nights of stay
2341 + if (different_lengths && (swap_dates_two[counter] || 0) && !swap_dates_one.includes(swap_dates_two[counter])) {
2342 + // moving a shorter reservation onto a longer reservation
2343 + // remove the children elements from this longer reservation
2344 + current_cell.children().not('.vbo-tableaux-booking-checkout').not('.vbo-roomdaynote-trigger').remove();
2345 + // remove the occupied class as well
2346 + current_cell.removeClass('subroom-busy').addClass('subnotbusy').attr('data-bids', '');
2347 + // set the occupied class onto the target
2348 + target_cell.removeClass('subnotbusy').addClass('subroom-busy');
2349 + // check booking color tags
2350 + let current_cell_styling = current_cell.attr('style');
2351 + // remove custom background color with an inline styling
2352 + current_cell.attr('style', '');
2353 + // restore the original styling on target
2354 + target_cell.attr('style', current_cell_styling);
2355 + }
2356 + // set next target cell
2357 + target_cell = target_cell.next();
2358 + // update current cell
2359 + current_cell = current_cell.next();
2360 + }
2361 +
2362 + // move the checkout snake of the second sub-unit
2363 + if (target_cell && target_cell.length && current_cell && current_cell.length && current_cell.find('.vbo-tableaux-booking-checkout').length) {
2364 + // make sure to move just the first check-out snake, because snakes with equal duration may have moved the check-out here already
2365 + current_cell.find('.vbo-tableaux-booking-checkout').first().prependTo(target_cell);
2366 + }
2367 +
2368 + // dispatch the event for the sub-units updated
2369 + VBOCore.emitEvent('vbo-overv-sub-units-updated');
2370 + },
2371 + (err) => {
2372 + alert(err.responseText);
2373 + console.error(err.responseText);
2374 + }
2375 + );
2376 +
2377 + return true;
2378 +}
2379 +
2380 +/**
2381 + * Open a booking in a new tab.
2382 + */
2383 +function vboOvervOpenBooking(bid) {
2384 + var open_url = jQuery('.vbo-basenavuri-details').attr('href');
2385 + open_url = open_url.replace('%d', bid);
2386 + // navigate in a new tab
2387 + window.open(open_url, '_blank');
2388 +}
2389 +
2390 +/**
2391 + * Resolves room assignment for a given booking.
2392 + */
2393 +function vboOvervResolveRoomAssignment(bid, broomid) {
2394 + // the moveset signature to apply, empty by default
2395 + let movesetSignature = null;
2396 +
2397 + // the number of reassignment solutions found
2398 + let solutionsFound = 0;
2399 +
2400 + // list of moveset signatures to skip
2401 + let skipMovesets = [];
2402 +
2403 + // list of booking IDs to skip
2404 + let skipBookings = [];
2405 +
2406 + // init modal body element
2407 + let modalBody = null;
2408 +
2409 + // define the function to find a reassignment solution
2410 + const findReassignmentFn = () => {
2411 + // show loading
2412 + VBOCore.emitEvent('vbo-loading-modal-overv-resolveroomassign');
2413 +
2414 + // make the request to get the bookings information
2415 + VBOCore.doAjax(
2416 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.resolve_room_assignment'); ?>",
2417 + {
2418 + bid: bid,
2419 + room_booking_id: broomid,
2420 + skip_booking_ids: skipBookings,
2421 + skip_moveset_signatures: skipMovesets,
2422 + },
2423 + (res) => {
2424 + // stop loading
2425 + VBOCore.emitEvent('vbo-loading-modal-overv-resolveroomassign');
2426 +
2427 + try {
2428 + let obj_res = typeof res === 'string' ? JSON.parse(res) : res;
2429 +
2430 + // update moveset signature to apply
2431 + movesetSignature = obj_res?.movesetSignature;
2432 +
2433 + // update number of solutions found so far
2434 + solutionsFound = obj_res?.solutionsCount || solutionsFound;
2435 +
2436 + // append HTML content
2437 + (modalBody[0] || modalBody).innerHTML = obj_res?.html;
2438 +
2439 + // show modal apply button group
2440 + applyBtnGroup.style.display = '';
2441 + } catch(e) {
2442 + console.error(e);
2443 + alert('Invalid response.');
2444 + }
2445 + },
2446 + (err) => {
2447 + // display error message
2448 + alert(err.responseText || err);
2449 +
2450 + // dismiss modal
2451 + VBOCore.emitEvent('vbo-dismiss-modal-overv-resolveroomassign');
2452 + }
2453 + );
2454 + };
2455 +
2456 + // define the function to apply a moveset signature
2457 + const applyReassignmentFn = (useSignature) => {
2458 + // determine the signature to use
2459 + useSignature = useSignature || movesetSignature;
2460 +
2461 + // show loading
2462 + VBOCore.emitEvent('vbo-loading-modal-overv-resolveroomassign');
2463 +
2464 + // make the request to apply the moveset
2465 + VBOCore.doAjax(
2466 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.apply_room_assignment'); ?>",
2467 + {
2468 + moveset: useSignature,
2469 + },
2470 + (res) => {
2471 + // dismiss modal
2472 + VBOCore.emitEvent('vbo-dismiss-modal-overv-resolveroomassign');
2473 +
2474 + // reload the page
2475 + location.reload();
2476 + },
2477 + (err) => {
2478 + // display error message
2479 + alert(err.responseText || err);
2480 +
2481 + // stop loading
2482 + VBOCore.emitEvent('vbo-loading-modal-overv-resolveroomassign');
2483 + }
2484 + );
2485 + };
2486 +
2487 + // define the function to apply the reassignment "preview"
2488 + const previewReassignmentFn = () => {
2489 + if (!movesetSignature) {
2490 + throw new Error('Empty moveset signature');
2491 + }
2492 +
2493 + // store the moveset and its status to the admin-dock as temporary data
2494 + VBOCore.getAdminDock().addTemporaryData(
2495 + {
2496 + id: '_tmp',
2497 + persist_id: 'reassignmentmoveset',
2498 + name: <?php echo json_encode(JText::translate('VBO_RESOLVE_ROOM_ASSIGNMENT')); ?>,
2499 + icon: '<?php VikBookingIcons::e('random'); ?>',
2500 + style: 'orange',
2501 + },
2502 + {
2503 + moveset: movesetSignature,
2504 + skipMovesets: skipMovesets,
2505 + skipBookings: skipBookings,
2506 + }
2507 + );
2508 +
2509 + // the "preview" will actually apply the moveset,
2510 + // but it will be possible to undo the changes
2511 + applyReassignmentFn();
2512 + };
2513 +
2514 + // build modal buttons
2515 + let cancelBtn = document.createElement('button');
2516 + cancelBtn.setAttribute('type', 'button');
2517 + cancelBtn.classList.add('btn');
2518 + cancelBtn.textContent = Joomla.JText._('VBANNULLA');
2519 + cancelBtn.addEventListener('click', () => {
2520 + VBOCore.emitEvent('vbo-dismiss-modal-overv-resolveroomassign');
2521 + });
2522 +
2523 + let applyBtn = document.createElement('button');
2524 + applyBtn.setAttribute('type', 'button');
2525 + applyBtn.classList.add('btn', 'btn-primary');
2526 + applyBtn.textContent = <?php echo json_encode(JText::translate('VBAPPLY')); ?>;
2527 + applyBtn.addEventListener('click', () => {
2528 + // apply moveset to reassign room numbers
2529 + applyReassignmentFn();
2530 + });
2531 +
2532 + let actionsBtn = document.createElement('button');
2533 + actionsBtn.setAttribute('type', 'button');
2534 + actionsBtn.classList.add('btn', 'btn-primary');
2535 + actionsBtn.innerHTML = '<?php VikBookingIcons::e('ellipsis-h'); ?>';
2536 +
2537 + // define the context menu for the actions button
2538 + jQuery(actionsBtn).vboContextMenu({
2539 + placement: 'top-right',
2540 + buttons: [
2541 + {
2542 + class: 'btngroup',
2543 + text: '',
2544 + disabled: true,
2545 + visible: function (root, event) {
2546 + // always overwrite title property
2547 + let baseText = <?php echo json_encode(JText::translate('VBO_NUM_SOLUTIONS_FOUND')); ?>;
2548 + this.text = baseText.replace('%d', solutionsFound);
2549 + return solutionsFound ? true : false;
2550 + },
2551 + },
2552 + {
2553 + class: 'vbo-context-menu-entry-secondary',
2554 + text: <?php echo json_encode(JText::translate('VBO_SUGG_ANOTHER_SOL')); ?>,
2555 + icon: '<?php echo VikBookingIcons::i('forward'); ?>',
2556 + separator: false,
2557 + action: (root, event) => {
2558 + // suggest another solution, push current moveset to skip
2559 + skipMovesets.push(movesetSignature);
2560 + // find all bookings set to be excluded
2561 + document.querySelectorAll('.vbo-room-reassignment-move-exclude[data-booking-id]').forEach((excludeEl) => {
2562 + let excludeInput = excludeEl.querySelector('input[type="checkbox"][name*="skip_booking_id"]');
2563 + if (excludeInput && excludeInput.checked) {
2564 + // push booking ID to exclude
2565 + skipBookings.push(excludeEl.getAttribute('data-booking-id'));
2566 + }
2567 + });
2568 + // call function to find another reassignment solution
2569 + findReassignmentFn();
2570 + },
2571 + },
2572 + {
2573 + class: 'vbo-context-menu-entry-secondary',
2574 + text: <?php echo json_encode(JText::translate('VBOPREVIEW')); ?>,
2575 + icon: '<?php echo VikBookingIcons::i('eye'); ?>',
2576 + separator: true,
2577 + action: (root, event) => {
2578 + // call function to build the preview
2579 + previewReassignmentFn();
2580 + },
2581 + },
2582 + {
2583 + class: 'vbo-context-menu-entry-secondary',
2584 + text: <?php echo json_encode(JText::translate('VBO_RESTART')); ?>,
2585 + icon: '<?php echo VikBookingIcons::i('play'); ?>',
2586 + action: (root, event) => {
2587 + // restart matrix search
2588 + skipMovesets = [];
2589 + skipBookings = [];
2590 + // call function to restart finding a reassignment solution
2591 + findReassignmentFn();
2592 + },
2593 + },
2594 + ],
2595 + });
2596 +
2597 + let applyBtnGroup = document.createElement('div');
2598 + applyBtnGroup.classList.add('btn-group', 'vbo-context-menu-btn-group', 'vbo-overv-resolveroomassign-actions');
2599 + applyBtnGroup.style.display = 'none';
2600 + applyBtnGroup.append(applyBtn);
2601 + applyBtnGroup.append(actionsBtn);
2602 +
2603 + // display modal
2604 + modalBody = VBOCore.displayModal({
2605 + suffix: 'overv-resolveroomassign',
2606 + extra_class: 'vbo-modal-rounded vbo-modal-tall',
2607 + title: <?php echo json_encode(JText::translate('VBO_RESOLVE_ROOM_ASSIGNMENT')); ?>,
2608 + draggable: true,
2609 + escape_dismiss: false,
2610 + footer_left: cancelBtn,
2611 + footer_right: applyBtnGroup,
2612 + dismiss_event: 'vbo-dismiss-modal-overv-resolveroomassign',
2613 + loading_event: 'vbo-loading-modal-overv-resolveroomassign',
2614 + loading_body: '<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>',
2615 + });
2616 +
2617 + // call function to find a reassignment solution
2618 + findReassignmentFn();
2619 +}
2620 +
2621 +/**
2622 + * DOM ready state
2623 + */
2624 +jQuery(function() {
2625 + // register to the event emitted when a new booking is created through an admin widget
2626 + document.addEventListener('vbo_new_booking_created', (e) => {
2627 + if (!e || !e.detail || !e.detail.hasOwnProperty('bid') || !e.detail['bid']) {
2628 + // do nothing
2629 + return;
2630 + }
2631 + // reload the page to display the new booking just created
2632 + location.reload();
2633 + });
2634 +
2635 + // register to the event dispatched when sub-units are moved
2636 + document.addEventListener('vbo-overv-sub-units-updated', (e) => {
2637 + // take care of the "unassigned" (missing sub-units) rows
2638 + document
2639 + .querySelectorAll('tr.vboverviewtablerow-subunit-unassigned')
2640 + .forEach((row) => {
2641 + let unassigned_cells = row.querySelectorAll('td.subroom-busy').length;
2642 + if (!unassigned_cells) {
2643 + try {
2644 + // get rid of the alert element first
2645 + let main_room_id = row.getAttribute('data-subroomid').split('-')[0];
2646 + row
2647 + .closest('table')
2648 + .querySelector('tr.vboverviewtablerow[data-roomid="' + main_room_id + '"]')
2649 + .querySelector('td.roomname')
2650 + .querySelector('.vbo-overview-alert')
2651 + .remove();
2652 + } catch(e) {
2653 + console.error(e);
2654 + }
2655 +
2656 + // remove the unassigned row for this room-type as there are no more bookings
2657 + row.remove();
2658 + }
2659 + });
2660 + });
2661 +
2662 + /* 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 */
2663 + jQuery("td.vbo-checkinday span.vbo-draggable-sp").each(function(k, v) {
2664 + var parentheight = jQuery(this).closest('td').height();
2665 + var spheight = jQuery(this).height();
2666 + var padsp = Math.floor((parentheight - spheight) / 2);
2667 + jQuery(this).css({"padding": padsp + "px 0"});
2668 + });
2669 +
2670 + /* Expand/Collapse tooltip */
2671 + jQuery(document.body).on("click", ".vbo-overview-expandtoggle", function() {
2672 + jQuery(this).closest('.vbo-overview-tipblock').toggleClass('vbo-overview-tipblock-expanded');
2673 + });
2674 +
2675 + /* DnD Start */
2676 +
2677 + /* DnD Event dragstart */
2678 + jQuery(document.body).on("dragstart", "td.vbo-checkinday span, td.vbo-checkinday .vbo-tableaux-booking-checkin", function(e) {
2679 + // start dragging and prevent tooltip from popping up
2680 + isdragging = true;
2681 + if (hovtip === true) {
2682 + hideVboTooltip();
2683 + }
2684 +
2685 + let parentcell = jQuery(this).closest('td');
2686 + if (parentcell.hasClass('busytmplock') || parentcell.hasClass('busy-sharedcalendar')) {
2687 + return false;
2688 + }
2689 +
2690 + // reset pool of booked date cells
2691 + cellspool = [];
2692 +
2693 + let dt = e.originalEvent.dataTransfer;
2694 + let bidstart = parentcell.attr('data-bids');
2695 + let checkind = parentcell.attr('data-day');
2696 + let roomid = parentcell.parent('tr').find('td').first().attr('data-roomid');
2697 + if (!bidstart.length || !checkind.length || !roomid.length) {
2698 + return false;
2699 + }
2700 +
2701 + // build the drag preview to set
2702 + let dragTarget = e.originalEvent.target;
2703 + if (dragTarget && !dragTarget.matches('.vbo-tableaux-booking')) {
2704 + // attempt to get the parent element
2705 + dragTarget = dragTarget.closest('.vbo-tableaux-booking');
2706 + }
2707 + if (dragTarget && dragTarget.matches('.vbo-tableaux-booking')) {
2708 + // build custom drag node
2709 + let dragImage = dragTarget.cloneNode(true);
2710 + dragImage.style.position = 'absolute';
2711 + dragImage.style.top = '-9999px';
2712 + dragImage.style.left = '-9999px';
2713 + dragImage.style.width = getComputedStyle(dragTarget).width;
2714 + dragImage.classList.add('vbo-booking-drag-preview');
2715 +
2716 + // temporarily append the drag node to the body
2717 + document.body.appendChild(dragImage);
2718 +
2719 + // use the drag node as drag preview
2720 + dt.setDragImage(dragImage, 10, 10);
2721 +
2722 + // remove drag node from body
2723 + setTimeout(() => {
2724 + document.body.removeChild(dragImage);
2725 + }, 0);
2726 + }
2727 +
2728 + // count booking nights and set booked date cells
2729 + let totnights = countBookingCells(parentcell, bidstart, roomid);
2730 +
2731 + // add dragging class
2732 + if (jQuery(this).find('.vbo-tableaux-guest-name').length) {
2733 + jQuery(this).find('.vbo-tableaux-guest-name').addClass('vbo-dragging-sp');
2734 + } else {
2735 + if (!jQuery(this).hasClass('vbo-tableaux-booking-avatar')) {
2736 + jQuery(this).addClass('vbo-dragging-sp');
2737 + }
2738 + }
2739 +
2740 + // check if the sub-unit temporary row should be displayed
2741 + let room_id_int = Math.abs(parseInt(roomid));
2742 + if (!isNaN(room_id_int) && room_id_int) {
2743 + document
2744 + .querySelectorAll('.vboverviewtablerow-subunit-tmp[data-subroomid="' + room_id_int + '-0"]')
2745 + .forEach((room_tmp_row) => {
2746 + // display possibly hidden row with a small delay in order to not break dragging on some browsers
2747 + setTimeout(() => {
2748 + room_tmp_row.style.display = 'table-row';
2749 + }, 100);
2750 + });
2751 + }
2752 +
2753 + // populate dataTransfer data
2754 + let parent_rid = '';
2755 + let subunit_id = '';
2756 + if (parentcell.hasClass('subroom-busy')) {
2757 + let subdata = parentcell.parent('tr').attr('data-subroomid').split('-');
2758 + if (!subdata[1] && subdata[2]) {
2759 + // negative index split, probably for the unassigned row
2760 + subdata[1] = '-' + subdata[2];
2761 + }
2762 + parent_rid = subdata[0];
2763 + subunit_id = subdata[1];
2764 + }
2765 + dt.setData('Checkin', checkind);
2766 + dt.setData('Roomid', roomid);
2767 + dt.setData('Bid', bidstart);
2768 + dt.setData('Nights', totnights);
2769 + dt.setData('Parentrid', parent_rid);
2770 + dt.setData('Subunit', subunit_id);
2771 + });
2772 +
2773 + /* DnD Events dragenter dragover drop */
2774 + jQuery(document.body).on("dragenter dragover drop", "td.notbusy, td.busy, td.subnotbusy, td.subroom-busy", function(e) {
2775 + // always prevent default
2776 + e.preventDefault();
2777 +
2778 + // gather dragging cell data
2779 + let dt = e.originalEvent.dataTransfer;
2780 + let checkind = dt.getData('Checkin');
2781 + let roomid = dt.getData('Roomid');
2782 + let bid = dt.getData('Bid');
2783 + let totnights = dt.getData('Nights');
2784 + let parent_rid = dt.getData('Parentrid');
2785 + let subunit_id = dt.getData('Subunit');
2786 +
2787 + // check if we are hovering something that requires styling
2788 + if (e.type === 'dragover') {
2789 + let hover_cell = jQuery(this);
2790 + if (hover_cell.hasClass('subroom-busy') && hover_cell.hasClass('vbo-checkinday')) {
2791 + if (hover_cell.attr('data-day') != checkind && totnights > 1) {
2792 + hover_cell.find('.vbo-tableaux-booking-checkin').addClass('vbo-droppable-cell');
2793 + }
2794 + }
2795 + // do not proceed any further
2796 + return;
2797 + }
2798 +
2799 + // only look for drop
2800 + if (e.type !== 'drop') {
2801 + // ignore if we are not dropping the element
2802 + return;
2803 + }
2804 +
2805 + // always reset pool of cells
2806 + newcellspool = [];
2807 +
2808 + /* check if drop is allowed */
2809 + let landrow = jQuery(this).closest('tr');
2810 + let landrid = landrow.find('td').first().attr('data-roomid');
2811 + let landbids = jQuery(this).attr('data-bids');
2812 + if (landrid === undefined || !landrid.length) {
2813 + return false;
2814 + }
2815 +
2816 + if (jQuery(this).hasClass('busy') && !jQuery(this).hasClass('vbo-partially')) {
2817 + if (bid != landbids) {
2818 + // landed on an occupied date but not for the same booking ID so drop is not allowed on this day
2819 + return false;
2820 + }
2821 + }
2822 +
2823 + // build pool of cells
2824 + let freenights = countCellFreeNights(jQuery(this), landrid, totnights, bid);
2825 + if (freenights < totnights) {
2826 + alert(Joomla.JText._('VBOVWDNDERRNOTENCELLS').replace('%s', freenights).replace('%d', totnights));
2827 + return false;
2828 + }
2829 +
2830 + // collect the new dates
2831 + let nowdatefrom = jQuery(newcellspool[0]).attr('data-day');
2832 + let nowdateto = jQuery(newcellspool[(newcellspool.length -1)]).attr('data-day');
2833 +
2834 + // check if we are just moving a sub-unit onto another for the same dates
2835 + if (jQuery(this).hasClass('subnotbusy') || jQuery(this).hasClass('subroom-busy')) {
2836 + // dropping on a sub-unit is only allowed onto the same room ID, for the same dates (switch room index only)
2837 + let subdata = landrow.attr('data-subroomid').split('-');
2838 + if (!subdata[1] && subdata[2]) {
2839 + // negative index split, probably for the unassigned row
2840 + subdata[1] = '-' + subdata[2];
2841 + }
2842 + if (!subdata || !parent_rid || !subunit_id || parent_rid != subdata[0] || !subdata[1] || subunit_id == subdata[1]) {
2843 + // display proper error
2844 + if (subdata[1] && subunit_id == subdata[1]) {
2845 + // must use the same dates for sub-units
2846 + alert(Joomla.JText._('VBO_DRAG_SUBUNITS_SAMEDATE'));
2847 + } else {
2848 + // only sub-units can be moved onto rows for sub-units
2849 + alert(Joomla.JText._('VBO_ERR_SUBUN_MOVE_SUBUN'));
2850 + }
2851 + return false;
2852 + }
2853 +
2854 + // sub-unit drag&drop is only allowed on the same date to change sub-unit index
2855 + if (nowdatefrom != checkind) {
2856 + alert(Joomla.JText._('VBO_DRAG_SUBUNITS_SAMEDATE'));
2857 + return false;
2858 + }
2859 +
2860 + // attempt to perform the switch-room-index request
2861 + if (jQuery(this).hasClass('subnotbusy')) {
2862 + // move sub-unit to a different index
2863 + if (!vboMoveSubunit(bid.replaceAll('-', ''), parent_rid, subunit_id, subdata[1], checkind)) {
2864 + return false;
2865 + }
2866 + } else {
2867 + // dropped on an occupied sub-unit, so ask for swap confirmation
2868 + if (!vboSwapRoomSubunits(bid.replaceAll('-', ''), landbids.replaceAll('-', ''), parent_rid, subunit_id, subdata[1], checkind)) {
2869 + return false;
2870 + }
2871 + }
2872 +
2873 + // do not proceed
2874 + return;
2875 + }
2876 +
2877 + /* populate temporary class for the new cells */
2878 + jQuery(cellspool).each(function(k, v) {
2879 + v.addClass('vbo-dragging-cells-tmp');
2880 + });
2881 +
2882 + // bookings for multiple rooms should add the same dragging class also to the booking for the other rooms
2883 + let samebidcells = jQuery("td.vbo-checkinday[data-bids='" + bid + "']");
2884 + if (samebidcells.length > 1) {
2885 + jQuery("td[data-bids='" + bid + "']").each(function(k, v) {
2886 + if (!jQuery(v).hasClass('vbo-dragging-cells-tmp')) {
2887 + jQuery(v).addClass('vbo-dragging-cells-tmp');
2888 + }
2889 + });
2890 + }
2891 + jQuery(newcellspool).each(function(k, v) {
2892 + v.addClass('vbo-dragged-cells-tmp');
2893 + });
2894 +
2895 + /* populate and showing loading message */
2896 + jQuery('.vbo-loading-dnd-head').text(Joomla.JText._('VBOVWDNDMOVINGBID').replace('%d', bid));
2897 + let movingmess = '';
2898 + if (roomid != landrid) {
2899 + movingmess += Joomla.JText._('VBOVWDNDMOVINGROOM').replace('%s', jQuery("td[data-roomid='"+landrid+"']").first().find('span.vbo-overview-roomname').text() );
2900 + }
2901 + if (nowdatefrom != jQuery(cellspool[0]).attr('data-day') || nowdateto != jQuery(cellspool[(cellspool.length -1)]).attr('data-day')) {
2902 + if (roomid != landrid) {
2903 + movingmess += ', ';
2904 + }
2905 + movingmess += Joomla.JText._('VBOVWDNDMOVINGDATES').replace('%s', nowdatefrom + ' - ' + nowdateto);
2906 + }
2907 +
2908 + // hide tooltip, if any
2909 + hideVboTooltip();
2910 +
2911 + // display moving message
2912 + jQuery('.vbo-loading-dnd-body').text(movingmess);
2913 + jQuery('.vbo-info-overlay-block').fadeIn();
2914 + jQuery('.vbo-loading-dnd-footer').show();
2915 +
2916 + /* fire the Ajax request after 1.5 seconds just for giving visibility to the loading message */
2917 + setTimeout(function() {
2918 + doAlterBooking(bid, roomid, landrid);
2919 + }, 1500);
2920 + });
2921 +
2922 + /* DnD Event dragend: remove class, attribute and dataTransfer Data if drag ends on the same position as dragstart or onto an invalid date */
2923 + jQuery(document.body).on("dragend", "td.vbo-checkinday span, td.vbo-checkinday .vbo-tableaux-booking-checkin", function(e) {
2924 + // stop dragging to restore tooltip functions
2925 + isdragging = false;
2926 + // we could also access the originally dragged cell
2927 + let dt = e.originalEvent.dataTransfer;
2928 + // restore temporary classes
2929 + jQuery('.vbo-dragging-sp').removeClass('vbo-dragging-sp');
2930 + jQuery('.vbo-droppable-cell').removeClass('vbo-droppable-cell');
2931 + });
2932 + /* DnD End */
2933 +
2934 + /* Show New Reservation Form & Modal with bookings - Start */
2935 + jQuery('td.busy, td.notbusy').dblclick(function() {
2936 + var curday = jQuery(this).attr('data-day');
2937 + var roomid = jQuery(this).parent('tr').find('td.roomname').attr('data-roomid');
2938 + roomid = roomid && roomid.length ? roomid : '';
2939 + last_room_click = '';
2940 + last_date_click = '';
2941 + next_date_click = '';
2942 + vboCallOpenJModal('vbo-new-res', 'index.php?option=com_vikbooking&task=calendar&cid[]='+roomid+'&checkin='+curday+'&overv=1&tmpl=component', {room_id: roomid, checkin: curday});
2943 + });
2944 +
2945 + jQuery(document.body).on('click', 'td.busy, td.notbusy, td.busytmplock, td.subroom-busy', function(e) {
2946 + if (jQuery(this).hasClass('vbo-widget-booskcal-cell-mday')) {
2947 + return;
2948 + }
2949 +
2950 + if (jQuery(this).hasClass('busy') || jQuery(this).hasClass('busytmplock') || jQuery(this).hasClass('subroom-busy')) {
2951 + // make sure the clicked target is not inside the tooltip
2952 + if (e && e.target) {
2953 + if (jQuery(e.target).closest('.vbo-overview-tipblock').length) {
2954 + // abort when click originated from the tooltip
2955 + return;
2956 + }
2957 + if (jQuery(e.target).is('a')) {
2958 + // abort when click is made on a link (View choosebusy)
2959 + return;
2960 + }
2961 + if (jQuery(e.target).hasClass('vbo-roomdaynote-display') || jQuery(e.target).hasClass('vbo-roomdaynote-trigger')) {
2962 + // abort when click is made on a room-day note
2963 + return;
2964 + }
2965 + }
2966 +
2967 + // trigger mouseleave event to prevent tooltip from showing
2968 + jQuery(this).trigger('mouseleave');
2969 +
2970 + // get room name, date and day-bids
2971 + let room_name = '';
2972 + let sub_room_data = '';
2973 + let room_id = 0;
2974 + if (jQuery(this).hasClass('subroom-busy')) {
2975 + sub_room_data = jQuery(this).parent('tr').attr('data-subroomid');
2976 + room_id = sub_room_data.split('-')[0];
2977 + room_name = jQuery('td.roomname[data-roomid="' + room_id + '"]').not('.subroomname').first().find('.vbo-overview-roomname').text();
2978 + } else {
2979 + let main_room_cell = jQuery(this).parent('tr').find('td.roomname');
2980 + room_id = main_room_cell.attr('data-roomid');
2981 + room_name = main_room_cell.find('.vbo-overview-roomname').text();
2982 + }
2983 + let date_ymd = jQuery(this).attr('data-day');
2984 + let date_read = jQuery('.bluedays[data-ymd="' + date_ymd + '"]').attr('data-readymd');
2985 + let date_bids = jQuery(this).attr('data-bids');
2986 + let date_ubk = jQuery(this).attr('data-units-booked');
2987 + let date_ulft = jQuery(this).attr('data-units-left');
2988 + let def_bicon = '<?php VikBookingIcons::e('user', 'vbo-dashboard-guest-activity-avatar-icon'); ?>';
2989 + let closure_i = '<?php VikBookingIcons::e('ban', 'vbo-dashboard-guest-activity-avatar-icon'); ?>';
2990 +
2991 + let sub_room_index = sub_room_data ? (sub_room_data.split('-')[1] || '') : '';
2992 +
2993 + // build new reservation button
2994 + let newResBtn = null;
2995 + if (date_ubk > 1 || date_ulft > 1 || sub_room_data) {
2996 + newResBtn = document.createElement('button');
2997 + newResBtn.setAttribute('type', 'button');
2998 + newResBtn.classList.add('btn', 'btn-primary');
2999 + newResBtn.textContent = <?php echo json_encode(JText::translate('VBOSHOWQUICKRES')); ?>;
3000 + newResBtn.addEventListener('click', () => {
3001 + // dismiss modal
3002 + VBOCore.emitEvent('vbo-dismiss-modal-overv-rdaybookings');
3003 + // create new reservation
3004 + VBOCore.handleDisplayWidgetNotification({
3005 + widget_id: 'bookings_calendar',
3006 + }, {
3007 + id_room: room_id,
3008 + offset: date_ymd,
3009 + day: date_ymd,
3010 + newbook: 1,
3011 + });
3012 + });
3013 + }
3014 +
3015 + // display modal with booking details
3016 + let rday_bookings_modal_body = VBOCore.displayModal({
3017 + suffix: 'overv-rdaybookings',
3018 + extra_class: 'vbo-modal-rounded vbo-modal-tall' + (!newResBtn ? ' vbo-modal-nofooter' : ''),
3019 + title: room_name + (sub_room_index ? ' #' + sub_room_index : '') + ' - ' + date_read,
3020 + draggable: true,
3021 + footer_right: newResBtn,
3022 + dismiss_event: 'vbo-dismiss-modal-overv-rdaybookings',
3023 + loading_event: 'vbo-loading-modal-overv-rdaybookings',
3024 + loading_body: '<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>',
3025 + });
3026 +
3027 + // show loading
3028 + VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
3029 +
3030 + // make the request to get the bookings information
3031 + VBOCore.doAjax(
3032 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=getbookingsinfo'); ?>",
3033 + {
3034 + status: 'any',
3035 + idroom: room_id,
3036 + stay_date: date_ymd,
3037 + idorders: date_bids,
3038 + subroom: sub_room_data,
3039 + sharedcal: (jQuery(this).hasClass('busy-sharedcalendar') ? 1 : 0),
3040 + tmpl: 'component'
3041 + },
3042 + (res) => {
3043 + // stop loading
3044 + VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
3045 + try {
3046 + var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
3047 +
3048 + // build the HTML response nodes
3049 + var rday_bookings_wrap = jQuery('<div></div>').addClass('vbo-dashboard-guests-latest');
3050 + var rday_bookings_list = jQuery('<div></div>').addClass('vbo-dashboard-guest-messages-list');
3051 +
3052 + // flag to indicate if the button to toggle the cancelled reservations was displayed
3053 + var show_canc_res_flag = false;
3054 +
3055 + // loop through all bookings
3056 + for (var b in obj_res) {
3057 + if (!obj_res.hasOwnProperty(b)) {
3058 + continue;
3059 + }
3060 + // nights and guests
3061 + var nights_guests = [
3062 + obj_res[b]['roomsnum'] + ' ' + Joomla.JText._((obj_res[b]['roomsnum'] > 1 ? 'VBPVIEWORDERSTHREE' : 'VBEDITORDERTHREE')),
3063 + obj_res[b]['days'] + ' ' + Joomla.JText._((obj_res[b]['days'] > 1 ? 'VBDAYS' : 'VBDAY')),
3064 + obj_res[b]['tot_adults'] + ' ' + Joomla.JText._((obj_res[b]['tot_adults'] > 1 ? 'VBMAILADULTS' : 'VBMAILADULT'))
3065 + ];
3066 + if (obj_res[b]['tot_children'] > 0) {
3067 + nights_guests.push(obj_res[b]['tot_children'] + ' ' + Joomla.JText._((obj_res[b]['tot_children'] > 1 ? 'VBMAILCHILDREN' : 'VBMAILCHILD')));
3068 + }
3069 +
3070 + // OTA booking ID
3071 + var ota_bid_info = '';
3072 + if (obj_res[b].hasOwnProperty('idorderota') && obj_res[b].hasOwnProperty('channel') && obj_res[b]['idorderota'] && obj_res[b]['channel']) {
3073 + if (obj_res[b]['idorderota'].length <= 16) {
3074 + // try to display the information only for API channels
3075 + ota_bid_info = '<span class="label label-info">' + obj_res[b]['idorderota'] + '</span> ';
3076 + }
3077 + }
3078 +
3079 + // the separator between confirmed and cancelled reservations
3080 + var res_type_separator = null;
3081 +
3082 + // booking status and badge
3083 + var badge_type = 'warning';
3084 + if (obj_res[b]['status'] == 'confirmed') {
3085 + badge_type = 'success';
3086 + } else if (obj_res[b]['status'] == 'cancelled') {
3087 + badge_type = 'danger';
3088 + if (!show_canc_res_flag) {
3089 + // build the separator between confirmed and cancelled reservations to toggle the latter ones
3090 + res_type_separator = jQuery('<div></div>').addClass('vbo-bookings-status-separator');
3091 + var btn_separator = jQuery('<button></button>').attr('type', 'button').addClass('btn btn-small btn-secondary').text(Joomla.JText._('VBO_SHOW_CANCELLATIONS'));
3092 + btn_separator.on('click', function() {
3093 + jQuery(this).closest('.vbo-dashboard-guest-messages-list').find('[data-type="cancelled"]').toggle();
3094 + });
3095 + res_type_separator.append(btn_separator);
3096 + // turn flag on at the very first cancelled reservation found
3097 + show_canc_res_flag = true;
3098 + }
3099 + }
3100 +
3101 + // build main booking node
3102 + var rday_booking = jQuery('<div></div>').addClass('vbo-dashboard-guest-activity vbo-w-guestmessages-message');
3103 + if (badge_type == 'danger') {
3104 + // hide cancelled booking by default and set proper attribute
3105 + rday_booking.attr('data-type', 'cancelled').css('display', 'none');
3106 + }
3107 + rday_booking.attr('data-idorder', obj_res[b]['id']);
3108 + rday_booking.on('click', function(e) {
3109 + var click_target = jQuery(e.target);
3110 + if (click_target.is('a') || click_target.is('select') || click_target.is('option') || click_target.is('button')) {
3111 + return;
3112 + }
3113 + if ((click_target.is('span') && click_target.hasClass('label')) || click_target.is('img') || click_target.is('i')) {
3114 + // open booking details within the apposite admin-widget
3115 + e.stopPropagation();
3116 + VBOCore.handleDisplayWidgetNotification({
3117 + widget_id: 'booking_details',
3118 + }, {
3119 + booking_id: jQuery(this).attr('data-idorder'),
3120 + });
3121 + } else {
3122 + // open booking details on a new tab
3123 + vboOvervOpenBooking(jQuery(this).attr('data-idorder'));
3124 + }
3125 + });
3126 +
3127 + // build booking structure
3128 + var rday_booking_html = '';
3129 + rday_booking_html += '<div class="vbo-dashboard-guest-activity-avatar">' + "\n";
3130 + if (obj_res[b]['avatar_src']) {
3131 + rday_booking_html += '<img class="vbo-dashboard-guest-activity-avatar-profile" src="' + obj_res[b]['avatar_src'] + '" alt="' + obj_res[b]['avatar_alt'] + '" decoding="async" loading="lazy" />' + "\n";
3132 + } else if (obj_res[b]['closure']) {
3133 + rday_booking_html += closure_i + "\n";
3134 + } else {
3135 + rday_booking_html += def_bicon + "\n";
3136 + }
3137 + rday_booking_html += '</div>' + "\n";
3138 + rday_booking_html += '<div class="vbo-dashboard-guest-activity-content">' + "\n";
3139 + rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-head">' + "\n";
3140 + rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-info-details">' + "\n";
3141 + 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";
3142 + rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-info-icon">' + "\n";
3143 + rday_booking_html += ' <span class="badge badge-info">' + obj_res[b]['id'] + '</span> ' + "\n";
3144 + if (obj_res[b]['type'] && obj_res[b]['type'] == 'overbooking') {
3145 + rday_booking_html += ' <span class="label label-error vbo-label-overbooking">' + Joomla.JText._('VBO_BTYPE_OVERBOOKING') + '</span>' + "\n";
3146 + }
3147 + rday_booking_html += ' <span class="badge badge-' + badge_type + '">' + obj_res[b]['status_lbl'] + '</span>' + "\n";
3148 + rday_booking_html += ' <span class="vbo-w-guestmessages-message-staydates">' + "\n";
3149 + rday_booking_html += ' <?php VikBookingIcons::e('calendar-alt'); ?>' + "\n";
3150 + rday_booking_html += ' <span class="vbo-w-guestmessages-message-staydates-in">' + obj_res[b]['checkin_short'] + '</span>' + "\n";
3151 + rday_booking_html += ' <span class="vbo-w-guestmessages-message-staydates-sep">-</span>' + "\n";
3152 + rday_booking_html += ' <span class="vbo-w-guestmessages-message-staydates-out">' + obj_res[b]['checkout_short'] + '</span>' + "\n";
3153 + rday_booking_html += ' </span>' + "\n";
3154 + rday_booking_html += ' </div>' + "\n";
3155 + rday_booking_html += ' </div>' + "\n";
3156 + rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-info-date">' + "\n";
3157 + rday_booking_html += ' <span>' + obj_res[b]['book_date'] + '</span>' + "\n";
3158 + rday_booking_html += ' <span>' + obj_res[b]['book_time'] + '</span>' + "\n";
3159 + if (obj_res[b].hasOwnProperty('meals_included')) {
3160 + rday_booking_html += ' <div class="vbo-wider-badges-wrap">' + "\n";
3161 + obj_res[b]['meals_included'].forEach((meal_enum) => {
3162 + rday_booking_html += ' <div class="badge badge-info">' + meal_enum + '</div>' + "\n";
3163 + });
3164 + rday_booking_html += ' </div>' + "\n";
3165 + }
3166 + rday_booking_html += ' </div>' + "\n";
3167 + rday_booking_html += ' </div>' + "\n";
3168 + rday_booking_html += ' <div class="vbo-dashboard-guest-activity-content-info-msg">' + "\n";
3169 + rday_booking_html += ' <div>' + ota_bid_info + '<?php VikBookingIcons::e('bed'); ?> ' + nights_guests.join(', ') + '</div>' + "\n";
3170 + if (obj_res[b].hasOwnProperty('sub_units_data')) {
3171 + rday_booking_html += ' <div class="vbo-rdaybooking-subunits">' + "\n";
3172 + for (let sub_rname in obj_res[b]['sub_units_data']) {
3173 + if (!obj_res[b]['sub_units_data'].hasOwnProperty(sub_rname)) {
3174 + continue;
3175 + }
3176 + 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";
3177 + }
3178 + rday_booking_html += ' </div>' + "\n";
3179 + } else if (obj_res[b].hasOwnProperty('missing_index') && obj_res[b]['missing_index']) {
3180 + rday_booking_html += ' <div class="vbo-rdaybooking-subunits">' + "\n";
3181 + rday_booking_html += ' <span class="label label-warning">' + Joomla.JText._('VBO_MISSING_SUBUNIT') + '</span>' + "\n";
3182 + if (obj_res[b].hasOwnProperty('av_room_indexes')) {
3183 + // build drop downs to set the sub-unit to each room with no data
3184 + rday_booking_html += ' <div class="vbo-rdaybooking-subunits-list">' + "\n";
3185 + for (var av_rindex in obj_res[b]['av_room_indexes']) {
3186 + 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')) {
3187 + continue;
3188 + }
3189 + 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";
3190 + set_subunit_node += '<option value="">' + obj_res[b]['av_room_indexes'][av_rindex]['name'] + '</option>' + "\n";
3191 + for (var rindex_k in obj_res[b]['av_room_indexes'][av_rindex]['list']) {
3192 + if (!obj_res[b]['av_room_indexes'][av_rindex]['list'].hasOwnProperty(rindex_k)) {
3193 + continue;
3194 + }
3195 + set_subunit_node += '<option value="' + rindex_k + '">' + obj_res[b]['av_room_indexes'][av_rindex]['list'][rindex_k] + '</option>' + "\n";
3196 + }
3197 + set_subunit_node += '</select>' + "\n";
3198 + // append drop down
3199 + rday_booking_html += set_subunit_node;
3200 + }
3201 + rday_booking_html += ' </div>' + "\n";
3202 + } else {
3203 + // allow the system to relocate the room booking record
3204 + rday_booking_html += ' <div class="vbo-rdaybooking-subunits-list vbo-rdaybooking-subunit-relocate-wrap">' + "\n";
3205 + rday_booking_html += ' <button type="button" class="btn vbo-config-btn vbo-reassign-room-unit-btn" data-bid="' + obj_res[b]['id'] + '"><?php VikBookingIcons::e('random'); ?> ' + <?php echo json_encode(JText::translate('VBO_RESOLVE_ROOM_ASSIGNMENT')); ?> + '</button>' + "\n";
3206 + rday_booking_html += ' </div>' + "\n";
3207 + }
3208 + rday_booking_html += ' </div>' + "\n";
3209 + }
3210 + rday_booking_html += ' </div>' + "\n";
3211 + rday_booking_html += '</div>' + "\n";
3212 +
3213 + // set booking HTML to node
3214 + rday_booking.append(rday_booking_html);
3215 +
3216 + if (res_type_separator !== null) {
3217 + // append the separator to toggle the cancelled reservations
3218 + rday_bookings_list.append(res_type_separator);
3219 + }
3220 +
3221 + // append booking node to list
3222 + rday_bookings_list.append(rday_booking);
3223 + }
3224 +
3225 + // finalize response nodes
3226 + rday_bookings_wrap.append(rday_bookings_list);
3227 +
3228 + // append the response
3229 + rday_bookings_modal_body.append(rday_bookings_wrap);
3230 + } catch(err) {
3231 + console.error('could not parse JSON response', err, res);
3232 + alert('Could not parse JSON response');
3233 + }
3234 + },
3235 + (err) => {
3236 + // stop loading and display alert message
3237 + VBOCore.emitEvent('vbo-loading-modal-overv-rdaybookings');
3238 + console.error(err);
3239 + alert(err.responseText);
3240 + }
3241 + );
3242 + }
3243 +
3244 + if (jQuery(this).hasClass('busy') && !jQuery(this).hasClass('vbo-partially')) {
3245 + // abort when fully booked date
3246 + return;
3247 + }
3248 +
3249 + if (jQuery(this).hasClass('busytmplock') || jQuery(this).hasClass('subroom-busy')) {
3250 + // abort when pending record or sub-room
3251 + return;
3252 + }
3253 +
3254 + // update clicked dates info
3255 + var curday = jQuery(this).attr('data-day');
3256 + var roomid = jQuery(this).parent('tr').find('td.roomname').attr('data-roomid');
3257 + roomid = roomid && roomid.length ? roomid : '';
3258 + last_room_click = roomid;
3259 + if (!last_date_click) {
3260 + last_date_click = curday;
3261 + } else if (last_date_click && next_date_click) {
3262 + last_date_click = curday;
3263 + next_date_click = '';
3264 + } else {
3265 + var from_info = new Date(last_date_click);
3266 + var to_info = new Date(curday);
3267 + if (from_info.getTime() < to_info.getTime()) {
3268 + next_date_click = curday;
3269 + } else {
3270 + last_date_click = curday;
3271 + next_date_click = '';
3272 + }
3273 + }
3274 +
3275 + // button shake effect
3276 + jQuery('.vbo-ovrv-flt-butn').addClass('vbo-ovrv-flt-butn-shake');
3277 + setTimeout(function() {
3278 + jQuery('.vbo-ovrv-flt-butn').removeClass('vbo-ovrv-flt-butn-shake');
3279 + }, 1000);
3280 + });
3281 + /* Show New Reservation Form & Modal with bookings - End */
3282 +
3283 + /* Hover Tooltip Start */
3284 + jQuery('td.busy, td.busytmplock, td.subroom-busy').not('.vbo-widget-booskcal-cell-mday').hover(function() {
3285 + registerHoveringTooltip(this);
3286 + }, unregisterHoveringTooltip);
3287 +
3288 + jQuery(document.body).on('click', '.vbo-overview-tip-bctag', function() {
3289 + if (!jQuery(this).parent().find(".vbo-overview-tip-bctag-subtip").length) {
3290 + jQuery(".vbo-overview-tip-bctag-subtip").remove();
3291 + var cur_color = jQuery(this).attr("data-ctagcolor");
3292 + var cur_bid = jQuery(this).attr("data-bid");
3293 + jQuery(this).after("<div class=\"vbo-overview-tip-bctag-subtip\">"+bctags_tip+"</div>");
3294 + 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);
3295 + jQuery(this).parent().find(".vbo-overview-tip-bctag-subtip").find(".vbo-overview-tip-bctag-subtip-circle").attr('data-bid', cur_bid);
3296 + jQuery(".vbo-overview-tip-bctag-subtip .hasTooltip").tooltip();
3297 + } else {
3298 + jQuery(".vbo-overview-tip-bctag-subtip").remove();
3299 + }
3300 + });
3301 +
3302 + var applying_tag = false;
3303 +
3304 + jQuery(document.body).on('click', '.vbo-overview-tip-bctag-subtip-circle', function() {
3305 + if (applying_tag === true) {
3306 + return false;
3307 + }
3308 + applying_tag = true;
3309 + var clickelem = jQuery(this);
3310 + var ctagkey = clickelem.attr('data-ctagkey');
3311 + var bid = clickelem.attr('data-bid');
3312 + // set opacity to circles as loading
3313 + jQuery('.vbo-overview-tip-bctag-subtip-circle').css('opacity', '0.6');
3314 +
3315 + // perform the request
3316 + VBOCore.doAjax(
3317 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=setbookingtag'); ?>",
3318 + {
3319 + tmpl: "component",
3320 + idorder: bid,
3321 + tagkey: ctagkey
3322 + },
3323 + (res) => {
3324 + applying_tag = false;
3325 + if (res.indexOf('e4j.error') >= 0 ) {
3326 + alert(res.replace("e4j.error.", ""));
3327 + // restore loading opacity in circles
3328 + jQuery('.vbo-overview-tip-bctag-subtip-circle').css('opacity', '1');
3329 + } else {
3330 + // decode the response, if needed
3331 + let obj_res = typeof res === 'string' ? JSON.parse(res) : res;
3332 + // check view mode
3333 + if ('<?php echo $pbmode; ?>' === 'tags') {
3334 + // apply new color to all booking cells
3335 + document
3336 + .querySelectorAll('.vbo-hascolortag[data-lastbid="' + bid + '"]')
3337 + .forEach((bookingCell) => {
3338 + bookingCell.style.backgroundColor = obj_res.color;
3339 + let bookingCellLink = bookingCell.querySelector('a');
3340 + if (bookingCellLink) {
3341 + bookingCellLink.style.color = obj_res.fontcolor;
3342 + }
3343 + });
3344 + }
3345 + // hide tooltip
3346 + hideVboTooltip();
3347 + }
3348 + },
3349 + (err) => {
3350 + applying_tag = false;
3351 + alert("Request Failed");
3352 + // restore loading opacity in circles
3353 + jQuery('.vbo-overview-tip-bctag-subtip-circle').css('opacity', '1');
3354 + }
3355 + );
3356 + });
3357 +
3358 + jQuery(document).keydown(function(e) {
3359 + if ( e.keyCode == 27 ) {
3360 + if (hovtip === true) {
3361 + hideVboTooltip();
3362 + }
3363 + if (vbodialogorph_on === true) {
3364 + hideVboDialogOverv(1);
3365 + }
3366 + }
3367 + });
3368 +
3369 + jQuery(document).mouseup(function(e) {
3370 + if (!hovtip && !vbodialogorph_on) {
3371 + return;
3372 + }
3373 + if (hovtip) {
3374 + var vbo_overlay_cont = jQuery(".vbo-overview-tipblock");
3375 + if (!vbo_overlay_cont.is(e.target) && vbo_overlay_cont.has(e.target).length === 0) {
3376 + hideVboTooltip();
3377 + return;
3378 + }
3379 + if (jQuery(".vbo-overview-tip-bctag-subtip").length) {
3380 + var vbo_overlay_subtip_cont = jQuery(".vbo-overview-tip-bctag-wrap");
3381 + if (!vbo_overlay_subtip_cont.is(e.target) && vbo_overlay_subtip_cont.has(e.target).length === 0) {
3382 + jQuery(".vbo-overview-tip-bctag-subtip").remove();
3383 + return;
3384 + }
3385 + }
3386 + }
3387 + if (vbodialogorph_on) {
3388 + var vbo_overlay_cont = jQuery(".vbo-info-overlay-content");
3389 + if (!vbo_overlay_cont.is(e.target) && vbo_overlay_cont.has(e.target).length === 0) {
3390 + hideVboDialogOverv(1);
3391 + }
3392 + }
3393 + });
3394 + /* Hover Tooltip End */
3395 +
3396 + /* Toggle Sub-units */
3397 + jQuery('.vbo-overview-subroom-toggle').click(function() {
3398 + var roomid = jQuery(this).closest('td').attr('data-roomid');
3399 + if (jQuery(this).hasClass('vbo-overview-subroom-toggle-active')) {
3400 + 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');
3401 + // do not use .hide() or "display: none" may not work due to forced "display: table-row"
3402 + jQuery("td.subroomname[data-roomid='-" + roomid + "']").parent('tr').css('display', 'none');
3403 + // update storage object
3404 + var storage_subunits_status = VBOCore.storageGetItem('vbo_overv_subunits_status');
3405 + try {
3406 + storage_subunits_status = JSON.parse(storage_subunits_status);
3407 + storage_subunits_status = storage_subunits_status || {};
3408 + } catch(e) {
3409 + storage_subunits_status = {};
3410 + }
3411 + storage_subunits_status[roomid] = 0;
3412 + VBOCore.storageSetItem('vbo_overv_subunits_status', storage_subunits_status);
3413 + } else {
3414 + 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');
3415 + // do not use .show() or "display: block" may be added rather than "display: table-row"
3416 + jQuery("td.subroomname[data-roomid='-" + roomid + "']").parent('tr').not('.vboverviewtablerow-subunit-tmp').css('display', 'table-row');
3417 + // update storage object
3418 + var storage_subunits_status = VBOCore.storageGetItem('vbo_overv_subunits_status');
3419 + try {
3420 + storage_subunits_status = JSON.parse(storage_subunits_status);
3421 + storage_subunits_status = storage_subunits_status || {};
3422 + } catch(e) {
3423 + storage_subunits_status = {};
3424 + }
3425 + storage_subunits_status[roomid] = 1;
3426 + VBOCore.storageSetItem('vbo_overv_subunits_status', storage_subunits_status);
3427 + }
3428 + });
3429 +
3430 + /* Display sub-units based on storage status */
3431 + var storage_subunits_defstatus = VBOCore.storageGetItem('vbo_overv_subunits_status');
3432 + try {
3433 + storage_subunits_defstatus = JSON.parse(storage_subunits_defstatus);
3434 + storage_subunits_defstatus = storage_subunits_defstatus || {};
3435 + for (let rid in storage_subunits_defstatus) {
3436 + if (!storage_subunits_defstatus.hasOwnProperty(rid)) {
3437 + continue;
3438 + }
3439 + if (storage_subunits_defstatus[rid]) {
3440 + // trigger click event to display the sub-units
3441 + jQuery('td.roomname[data-roomid="' + rid + '"]').find('.vbo-overview-subroom-toggle').trigger('click');
3442 + }
3443 + }
3444 + } catch(e) {
3445 + // do nothing
3446 + }
3447 +
3448 + /* Default state for sticky table heads */
3449 + vboToggleStickyTableHeaders(<?php echo $long_list && $cookie_sticky_heads != 'off' ? 1 : 0; ?>);
3450 +
3451 + /**
3452 + * Check orphans (only if not disabled with the cookie).
3453 + *
3454 + * @deprecated 1.18.0 (J) - 1.8.0 (WP) disabled with the "false" condition below.
3455 + */
3456 + var hideorphans = false;
3457 + var buiscuits = document.cookie;
3458 + if (buiscuits.length) {
3459 + var hideorphansck = "vboHideOrphans=1";
3460 + if (buiscuits.indexOf(hideorphansck) >= 0) {
3461 + hideorphans = true;
3462 + }
3463 + }
3464 + if (false && !hideorphans && <?php echo $vbo_auth_pricing ? 'true' : 'false'; ?>) {
3465 + // make the request
3466 +
3467 + VBOCore.doAjax(
3468 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=orphanscount'); ?>",
3469 + {
3470 + from: '<?php echo date($df, $tsstart); ?>',
3471 + months: <?php echo $mnum; ?>,
3472 + tmpl: "component"
3473 + },
3474 + (res) => {
3475 + var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
3476 + var orphans_list = '';
3477 + for (var rid in obj_res) {
3478 + if (!obj_res.hasOwnProperty(rid)) {
3479 + continue;
3480 + }
3481 + orphans_list += '<div class="vbo-orphans-info-room">';
3482 + orphans_list += ' <h4 class="vbo-orphans-roomname">'+obj_res[rid]['name']+'</h4>';
3483 + orphans_list += ' <div class="vbo-orphans-info-dates">';
3484 + for (var dind in obj_res[rid]['rdates']) {
3485 + if (!obj_res[rid]['rdates'].hasOwnProperty(dind)) {
3486 + continue;
3487 + }
3488 + orphans_list += ' <div class="vbo-orphans-info-date">'+obj_res[rid]['rdates'][dind]+'</div>';
3489 + }
3490 + orphans_list += ' </div>';
3491 + orphans_list += ' <div class="vbo-orphans-info-btn">';
3492 + 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>';
3493 + orphans_list += ' </div>';
3494 + orphans_list += '</div>';
3495 + }
3496 + if (orphans_list.length) {
3497 + // show the modal
3498 + jQuery('.vbo-orphans-info-list').html(orphans_list);
3499 + jQuery('.vbo-orphans-overlay-block').fadeIn();
3500 + vbodialogorph_on = true;
3501 + }
3502 + },
3503 + (err) => {
3504 + console.error("orphanscount Request Failed");
3505 + }
3506 + );
3507 + }
3508 +
3509 + // fests
3510 + jQuery(document.body).on("click", "th.bluedays", function() {
3511 + if (jQuery(this).hasClass('skip-bluedays-click')) {
3512 + return;
3513 + }
3514 + var ymd = jQuery(this).attr('data-ymd');
3515 + var daytitle = jQuery(this).attr('data-readymd');
3516 + if (jQuery(this).hasClass('vbo-overv-festcell')) {
3517 + // cell has fests
3518 + if (!vboFests.hasOwnProperty(ymd)) {
3519 + return;
3520 + }
3521 + vboRenderFests(ymd, daytitle);
3522 + } else {
3523 + // let the admin create a new fest
3524 +
3525 + // update ymd key for the selected date, useful for adding new fests
3526 + jQuery('.vbo-overlay-fests-addnew').attr('data-ymd', ymd);
3527 +
3528 + // unset content and display modal for just adding a new fest
3529 + jQuery('.vbo-overlay-fests-list').html('');
3530 + var fests_modal = VBOCore.displayModal({
3531 + suffix: 'overv-mng-fests',
3532 + extra_class: 'vbo-modal-rounded vbo-modal-tall',
3533 + title: '<?php VikBookingIcons::e('star'); ?> <span>' + daytitle + '</span>',
3534 + footer_right: '<button type="button" class="btn btn-success" onclick="vboAddFest();">' + Joomla.JText._('VBSAVE') + '</button>',
3535 + dismiss_event: 'vbo-dismiss-modal-overv-mng-fests',
3536 + onDismiss: () => {
3537 + jQuery('.vbo-overv-mngfest-wrap').appendTo('.vbo-overv-mngfest-block');
3538 + },
3539 + });
3540 +
3541 + // set modal body
3542 + jQuery('.vbo-overv-mngfest-wrap').appendTo(fests_modal);
3543 + }
3544 + });
3545 +
3546 + // room-day notes
3547 + jQuery(document.body).on("click", ".vbo-roomdaynote-display", function() {
3548 + if (!jQuery(this).closest('.vbo-roomdaynote-trigger').length) {
3549 + return;
3550 + }
3551 + var daytitle = new Array;
3552 + var roomday_info = jQuery(this).closest('.vbo-roomdaynote-trigger').attr('data-roomday').split('_');
3553 + // readable day
3554 + var readymd = roomday_info[0];
3555 + if (jQuery('.bluedays[data-ymd="' + roomday_info[0] + '"]').length) {
3556 + readymd = jQuery('.bluedays[data-ymd="' + roomday_info[0] + '"]').attr('data-readymd');
3557 + }
3558 + daytitle.push(readymd);
3559 + // room name
3560 + if (jQuery('.roomname[data-roomid="' + roomday_info[1] + '"]').length) {
3561 + daytitle.push(jQuery('.roomname[data-roomid="' + roomday_info[1] + '"]').first().find('.vbo-overview-roomname').text());
3562 + }
3563 + // sub-unit
3564 + if (parseInt(roomday_info[2]) > 0 && jQuery('.subroomname[data-roomid="-' + roomday_info[1] + '"]').length) {
3565 + daytitle.push(jQuery('.subroomname[data-roomid="-' + roomday_info[1] + '"]').find('.vbo-overview-subroomname').eq((parseInt(roomday_info[2]) - 1)).text());
3566 + }
3567 +
3568 + // display modal
3569 + var rdaynotes_modal = VBOCore.displayModal({
3570 + suffix: 'overv-mng-rdaynotes',
3571 + extra_class: 'vbo-modal-rounded vbo-modal-tall',
3572 + title: '<?php VikBookingIcons::e('comment'); ?> <span>' + daytitle.join(', ') + '</span>',
3573 + footer_right: '<button type="button" class="btn btn-success" onclick="vboAddRoomDayNote();">' + Joomla.JText._('VBSAVE') + '</button>',
3574 + dismiss_event: 'vbo-dismiss-modal-overv-mng-rdaynotes',
3575 + onDismiss: () => {
3576 + jQuery('.vbo-overv-mngroomdaynotes-wrap').appendTo('.vbo-overv-mngroomdaynotes-block');
3577 + },
3578 + });
3579 +
3580 + // set modal body
3581 + jQuery('.vbo-overv-mngroomdaynotes-wrap').appendTo(rdaynotes_modal);
3582 +
3583 + // populate current room day notes
3584 + vboRenderRdayNotes(roomday_info[0], roomday_info[1], roomday_info[2], readymd);
3585 + });
3586 +
3587 + /**
3588 + * Check if the unassigned rows, if any, should be hid because of no missing sub-units.
3589 + */
3590 + document
3591 + .querySelectorAll('tr.vboverviewtablerow-subunit-unassigned')
3592 + .forEach((unassigned_row) => {
3593 + let unassigned_cells = unassigned_row.querySelectorAll('td.subroom-busy').length;
3594 + if (!unassigned_cells) {
3595 + // remove the unassigned row for this room-type
3596 + unassigned_row.remove();
3597 + } else {
3598 + // add an alert icon next to the main room row
3599 + try {
3600 + let main_room_id = unassigned_row.getAttribute('data-subroomid').split('-')[0];
3601 + let alert_elem = document.createElement('span');
3602 + alert_elem.classList.add('vbo-overview-alert');
3603 +
3604 + let alert_icon = document.createElement('span');
3605 + alert_icon.classList.add('badge', 'badge-warning');
3606 + alert_icon.innerHTML = '<?php VikBookingIcons::e('exclamation-triangle'); ?>';
3607 + alert_icon.addEventListener('click', () => {
3608 + // find the first room reservation record with missing sub-unit
3609 + let reassignBookingEl = unassigned_row.querySelector('td.subroom-busy.vbo-checkinday[data-lastbid]');
3610 + if (reassignBookingEl) {
3611 + // scroll the table to make the room booking cell visible
3612 + let mainTable = unassigned_row.closest('table');
3613 + let tableWrapper = mainTable.closest('.vbo-table-responsive');
3614 + let firstCellWidth = mainTable
3615 + .querySelector('tr.vboverviewtablerow[data-roomid="' + main_room_id + '"]')
3616 + .querySelector('td.roomname')
3617 + .offsetWidth;
3618 + tableWrapper.scrollTo({
3619 + left: reassignBookingEl.offsetLeft - firstCellWidth - reassignBookingEl.offsetWidth,
3620 + behavior: 'smooth',
3621 + });
3622 +
3623 + // find a reassignment solution for the first booking found
3624 + setTimeout(() => {
3625 + vboOvervResolveRoomAssignment(reassignBookingEl.getAttribute('data-lastbid'));
3626 + }, 200);
3627 + }
3628 + });
3629 +
3630 + alert_elem.append(alert_icon);
3631 +
3632 + unassigned_row
3633 + .closest('table')
3634 + .querySelector('tr.vboverviewtablerow[data-roomid="' + main_room_id + '"]')
3635 + .querySelector('td.roomname')
3636 + .querySelector('.vbo-overview-roomname')
3637 + .insertAdjacentElement('afterend', alert_elem);
3638 + } catch(e) {
3639 + console.error(e);
3640 + }
3641 +
3642 + // restore any previous temporary data for "previewing" a reassignment moveset applied
3643 + let previousMovesetData = VBOCore.getAdminDock().loadTemporaryData(
3644 + {
3645 + id: '_tmp',
3646 + persist_id: 'reassignmentmoveset',
3647 + },
3648 + (movesetData) => {
3649 + // temporary data restored from dock
3650 + let closeBtn = document.createElement('button');
3651 + closeBtn.setAttribute('type', 'button');
3652 + closeBtn.classList.add('btn');
3653 + closeBtn.textContent = <?php echo json_encode(JText::translate('VBO_KEEP_CHANGES')); ?>;
3654 + closeBtn.addEventListener('click', () => {
3655 + // dismiss modal and keep changes
3656 + VBOCore.emitEvent('vbo-dismiss-modal-overv-resolveroomassign-undo');
3657 + });
3658 +
3659 + let undoBtn = document.createElement('button');
3660 + undoBtn.setAttribute('type', 'button');
3661 + undoBtn.classList.add('btn', 'btn-danger');
3662 + undoBtn.textContent = <?php echo json_encode(JText::translate('VBO_UNDO_CHANGES')); ?>;
3663 + undoBtn.addEventListener('click', () => {
3664 + // start loading
3665 + VBOCore.emitEvent('vbo-loading-modal-overv-resolveroomassign-undo');
3666 +
3667 + // make the request to undo the changes made by the moveset
3668 + VBOCore.doAjax(
3669 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.apply_room_assignment'); ?>",
3670 + {
3671 + moveset: movesetData?.moveset,
3672 + undo: 1,
3673 + },
3674 + (res) => {
3675 + // dismiss modal
3676 + VBOCore.emitEvent('vbo-dismiss-modal-overv-resolveroomassign-undo');
3677 +
3678 + // reload the page
3679 + location.reload();
3680 + },
3681 + (err) => {
3682 + // display error message
3683 + alert(err.responseText || err);
3684 +
3685 + // stop loading
3686 + VBOCore.emitEvent('vbo-loading-modal-overv-resolveroomassign-undo');
3687 + }
3688 + );
3689 + });
3690 +
3691 + // display prompt modal to apply or undo the changes just made
3692 + VBOCore.displayModal({
3693 + suffix: 'overv-resolveroomassign-undo',
3694 + extra_class: 'vbo-modal-rounded vbo-modal-prompt',
3695 + title: <?php echo json_encode(JText::translate('VBO_RESOLVE_ROOM_ASSIGNMENT')); ?>,
3696 + body: <?php echo json_encode(sprintf('%s / %s?', JText::translate('VBO_KEEP_CHANGES'), JText::translate('VBO_UNDO_CHANGES'))); ?>,
3697 + draggable: true,
3698 + escape_dismiss: false,
3699 + footer_left: undoBtn,
3700 + footer_right: closeBtn,
3701 + dismiss_event: 'vbo-dismiss-modal-overv-resolveroomassign-undo',
3702 + loading_event: 'vbo-loading-modal-overv-resolveroomassign-undo',
3703 + loading_body: '<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>',
3704 + });
3705 + },
3706 + (movesetData) => {
3707 + // temporary data removed from dock
3708 + // do nothing as moveset was already applied
3709 + }
3710 + );
3711 + }
3712 + });
3713 +
3714 + /**
3715 + * Add body click event delegation for elements that may be later added to the DOM.
3716 + */
3717 + document.body.addEventListener('click', (e) => {
3718 +
3719 + // resolve room assignment button in modal window
3720 + if (e.target.matches('.vbo-reassign-room-unit-btn[data-bid]') || e.target.closest('.vbo-reassign-room-unit-btn[data-bid]')) {
3721 + const btnEl = !e.target.matches('.vbo-reassign-room-unit-btn[data-bid]') ? e.target.closest('.vbo-reassign-room-unit-btn[data-bid]') : e.target;
3722 + const bid = btnEl.getAttribute('data-bid');
3723 +
3724 + // dismiss modal
3725 + VBOCore.emitEvent('vbo-dismiss-modal-overv-rdaybookings');
3726 +
3727 + // call the function that handles the room assignment for a specific booking
3728 + setTimeout(() => {
3729 + vboOvervResolveRoomAssignment(bid);
3730 + }, 200);
3731 +
3732 + // do not proceed
3733 + return;
3734 + }
3735 +
3736 + // resolve room assignment modal window include/exclude bookings checkbox
3737 + if (e.target.matches('input[type="checkbox"][name*="skip_booking_id"]')) {
3738 + const spanBtn = e.target.closest('.vbo-toggle-double-status');
3739 + if (spanBtn) {
3740 + if (e.target.checked) {
3741 + spanBtn.setAttribute('data-tooltiptext', spanBtn.getAttribute('data-text-disabled'));
3742 + } else {
3743 + spanBtn.setAttribute('data-tooltiptext', spanBtn.getAttribute('data-text-enabled'));
3744 + }
3745 + }
3746 +
3747 + // do not proceed
3748 + return;
3749 + }
3750 +
3751 + });
3752 +
3753 +<?php
3754 +/**
3755 + * Handle fixed table heads when on snake-layout (scroll), which is completely
3756 + * different than the table-layout sticky table headers (handled through jQuery).
3757 + *
3758 + * @since 1.16.9 (J) - 1.6.9 (WP)
3759 + */
3760 +if ($cookie_sticky_heads == 'off') {
3761 + ?>
3762 + // listen to the document scroll event to handle the fixed table heads
3763 + document.addEventListener('scroll', vboOvervHandleVScrollTableHeads);
3764 +
3765 + // scan all tables for listening to their horizontal scrolling event
3766 + let tables = document.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
3767 + tables.forEach((table) => {
3768 + table.parentNode.addEventListener('scroll', vboOvervHandleHScrollTableHead);
3769 + });
3770 +
3771 + // launch the handling function
3772 + vboOvervHandleHScrollTableHead();
3773 + <?php
3774 +}
3775 +?>
3776 +});
3777 +
3778 +/**
3779 + * Document vertical scroll event handler for the fixed table heads.
3780 + *
3781 + * @param object e the document scroll event object.
3782 + *
3783 + * @return void
3784 + *
3785 + * @since 1.16.9 (J) - 1.6.9 (WP)
3786 + */
3787 +function vboOvervHandleVScrollTableHeads(e) {
3788 + // register throttling callback
3789 + VBOCore.throttleTimer(() => {
3790 + // get all eligible tables
3791 + let tables = document.querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
3792 + if (!tables.length) {
3793 + // un-register event
3794 + document.removeEventListener('scroll', vboOvervHandleVScrollTableHeads);
3795 + return;
3796 + }
3797 +
3798 + // pool of fixed table heads
3799 + let fixedTableHeadNodes = vboOvervBuildFixedTableHeads(tables);
3800 +
3801 + if (fixedTableHeadNodes.length) {
3802 + // append to body just the last table head node for the bottom farthest table
3803 + document.body.appendChild(fixedTableHeadNodes[fixedTableHeadNodes.length - 1]);
3804 + }
3805 + }, 400);
3806 +}
3807 +
3808 +/**
3809 + * Table-element horizontal scroll event handler for updating the fixed table heads.
3810 + *
3811 + * @param object e the table-element scroll event object.
3812 + *
3813 + * @return void
3814 + *
3815 + * @since 1.16.9 (J) - 1.6.9 (WP)
3816 + */
3817 +function vboOvervHandleHScrollTableHead(e) {
3818 + // register throttling callback
3819 + VBOCore.throttleTimer(() => {
3820 + // horizontal scrolling happens on the parent DIV node
3821 + let tables = (e?.target || document).querySelectorAll('table.vboverviewtable.vbo-overv-sticky-table-head-off');
3822 + if (tables.length !== 1) {
3823 + // un-register event for missing table
3824 + if (e?.target) {
3825 + e.target.removeEventListener('scroll', vboOvervHandleHScrollTableHead);
3826 + }
3827 + return;
3828 + }
3829 +
3830 + if (<?php echo $table_scroll_layout === 'inline' ? 'true' : 'false'; ?>) {
3831 + // the inline-scroll layout requires to update the visible month name upon scrolling
3832 + let firstVisibleCell = vboOvervFirstVisibleTableHeadNode(tables[0]);
3833 + let periodEl = tables[0].querySelector('.bluedays.vbo-overview-month[data-initial-period]');
3834 + if (firstVisibleCell && periodEl) {
3835 + if (!firstVisibleCell.no_scroll && vboOvervMonthNames[firstVisibleCell.mon]) {
3836 + // display current scroll position month name
3837 + periodEl.textContent = vboOvervMonthNames[firstVisibleCell.mon] + ' ' + firstVisibleCell.year;
3838 + } else {
3839 + // restore the original range/month name
3840 + periodEl.textContent = periodEl.getAttribute('data-initial-period');
3841 + }
3842 + }
3843 + }
3844 +
3845 + // pool of fixed table heads
3846 + let fixedTableHeadNodes = vboOvervBuildFixedTableHeads(tables);
3847 +
3848 + if (fixedTableHeadNodes.length) {
3849 + // append to body the first and only table head node expected
3850 + document.body.appendChild(fixedTableHeadNodes[0]);
3851 + }
3852 + }, 200);
3853 +}
3854 +
3855 +/**
3856 + * Builds the list of element nodes for the fixed table heads.
3857 + *
3858 + * @param object tables The table elements to parse.
3859 + *
3860 + * @return object[]
3861 + *
3862 + * @since 1.16.9 (J) - 1.6.9 (WP)
3863 + */
3864 +function vboOvervBuildFixedTableHeads(tables) {
3865 + // pool of fixed table heads
3866 + let fixedTableHeadNodes = [];
3867 +
3868 + // get document body scroll top value
3869 + let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
3870 +
3871 + // platform fixed offset top
3872 + let platformAdminBar = <?php echo VBOPlatformDetection::isWordPress() ? 'document.querySelector("#wpadminbar")' : '(document.querySelector(".navbar") || document.querySelector("#subhead-container"))'; ?>;
3873 + let fixedOffsetTop = platformAdminBar ? platformAdminBar.offsetHeight : 0;
3874 + if (fixedOffsetTop) {
3875 + // ensure the responsive mode has got a fixed/absolute positioning
3876 + let adminBarPosition = window.getComputedStyle(platformAdminBar).getPropertyValue('position') || platformAdminBar.style.position || '';
3877 + if (adminBarPosition != 'fixed' && adminBarPosition != 'absolute' && adminBarPosition != 'sticky') {
3878 + // no fixed admin bar
3879 + fixedOffsetTop = 0;
3880 + }
3881 + }
3882 +
3883 + // scan all table elements
3884 + (tables || []).forEach((table, index) => {
3885 + // always check for existing fixed table head element
3886 + let scrollingHeadElement = document.querySelector('.vbo-overv-sticky-scroll-month[data-table-index="' + index + '"]');
3887 + if (scrollingHeadElement) {
3888 + // remove fixed table head element from DOM, will be reconstructed if needed
3889 + scrollingHeadElement.remove();
3890 + }
3891 +
3892 + // get table head element node
3893 + let tableHeadNode = table.querySelector('tr.vboverviewtablerowone');
3894 +
3895 + // get position of current table head
3896 + let tableHeadHeight = tableHeadNode.offsetHeight;
3897 +
3898 + // make sure the table head is NOT visible, hence requires a fixed head
3899 + if (scrollTop > (table.offsetTop + tableHeadHeight - (fixedOffsetTop / 2))) {
3900 + if (scrollTop > (table.offsetTop + table.offsetHeight)) {
3901 + // scrolling went even below the table
3902 + return;
3903 + }
3904 +
3905 + // build main element node
3906 + let fixedTableHead = document.createElement('div');
3907 + fixedTableHead.className = 'vbo-overv-sticky-scroll-month';
3908 + fixedTableHead.style.top = fixedOffsetTop + 'px';
3909 + fixedTableHead.style.left = table.parentNode.getBoundingClientRect().x + 'px';
3910 + fixedTableHead.setAttribute('data-table-index', index);
3911 +
3912 + // inner element node
3913 + let fixedTableHeadWrap = document.createElement('div');
3914 + fixedTableHeadWrap.className = 'vbo-overv-sticky-scroll-month-wrap';
3915 +
3916 + // month-name element node
3917 + let tableHeadMonth = tableHeadNode.querySelector('.vbo-overview-month');
3918 + let fixedTableHeadMonth = document.createElement('div');
3919 + fixedTableHeadMonth.className = 'vbo-overv-sticky-scroll-month-m';
3920 + fixedTableHeadMonth.style.width = tableHeadMonth.offsetWidth + 'px';
3921 + fixedTableHeadMonth.appendChild(
3922 + document.createTextNode(tableHeadMonth.innerText)
3923 + );
3924 + fixedTableHeadWrap.appendChild(fixedTableHeadMonth);
3925 +
3926 + // obtain the list of visible month-day cells
3927 + vboOvervVisibleTableHeadNodes(table, index).forEach((mday, mdayIndex) => {
3928 + // month-day cell element node
3929 + let fixedTableHeadMday = document.createElement('div');
3930 + fixedTableHeadMday.className = 'vbo-overv-sticky-scroll-month-d';
3931 + if (mdayIndex && mday?.mday == 1) {
3932 + fixedTableHeadMday.classList.add('vbo-overv-sticky-scroll-newmonth-d');
3933 + }
3934 + fixedTableHeadMday.style.width = mday['vwidth'] + 'px';
3935 + fixedTableHeadMday.addEventListener('click', () => {
3936 + try {
3937 + // use jQuery to trigger the click event, or plain JS won't work
3938 + /*
3939 + document
3940 + .querySelector('table.vboverviewtable.vbo-overv-sticky-table-head-off[data-table-index="' + index + '"]')
3941 + .querySelector('.bluedays[data-ymd="' + mday['ymd'] + '"]')
3942 + .dispatchEvent(new Event('click'));
3943 + */
3944 + jQuery('table.vboverviewtable.vbo-overv-sticky-table-head-off[data-table-index="' + index + '"]')
3945 + .find('.bluedays[data-ymd="' + mday['ymd'] + '"]')
3946 + .trigger('click');
3947 + } catch(e) {
3948 + // do nothing
3949 + }
3950 + });
3951 +
3952 + // week-day element
3953 + let wdayEl = document.createElement('span');
3954 + wdayEl.className = 'vbo-overv-sticky-scroll-month-day';
3955 + wdayEl.appendChild(
3956 + document.createTextNode(mday['wday'])
3957 + );
3958 + fixedTableHeadMday.appendChild(wdayEl);
3959 +
3960 + // month-day element
3961 + let mdayEl = document.createElement('span');
3962 + mdayEl.className = 'vbo-overv-sticky-scroll-month-day-num';
3963 + mdayEl.appendChild(
3964 + document.createTextNode(mday['mday'])
3965 + );
3966 + fixedTableHeadMday.appendChild(mdayEl);
3967 +
3968 + // append month-day cell element node
3969 + fixedTableHeadWrap.appendChild(fixedTableHeadMday);
3970 + });
3971 +
3972 + // append wrapper
3973 + fixedTableHead.appendChild(fixedTableHeadWrap);
3974 +
3975 + // push the final element node to the pool
3976 + fixedTableHeadNodes.push(fixedTableHead);
3977 + }
3978 + });
3979 +
3980 + return fixedTableHeadNodes;
3981 +}
3982 +
3983 +/**
3984 + * Builds a list of visible month-day cells in a table head.
3985 + *
3986 + * @param object table the NodeList element representing a table.
3987 + * @param number index the NodeList element index we are parsing.
3988 + *
3989 + * @return object[]
3990 + *
3991 + * @since 1.16.9 (J) - 1.6.9 (WP)
3992 + */
3993 +function vboOvervVisibleTableHeadNodes(table, index) {
3994 + // get the main table head element
3995 + let tableHeadElement = table.querySelector('tr.vboverviewtablerowone');
3996 +
3997 + // get month element of the given table
3998 + let monthElement = tableHeadElement.querySelector('.vbo-overview-month');
3999 + let monthName = monthElement.innerText;
4000 + let monthLeft = monthElement.offsetLeft - 2;
4001 + let monthWidth = monthElement.offsetWidth;
4002 +
4003 + // get horizontal scroll left and table width from parent node
4004 + let tableScrollLeft = table.parentNode.scrollLeft;
4005 + let tableViewWidth = table.parentNode.offsetWidth;
4006 +
4007 + // pool of visible month-day cells
4008 + let visibleMonthDays = [];
4009 +
4010 + // scan all visible month-day elements
4011 + tableHeadElement.querySelectorAll('.bluedays:not(.vbo-overview-month)').forEach((cell) => {
4012 + let monthDayLeft = cell.offsetLeft;
4013 + let monthDayWidth = cell.offsetWidth;
4014 +
4015 + // ensure cell is visible from left scroll
4016 + if ((monthDayLeft + monthDayWidth) >= (monthLeft + monthWidth)) {
4017 + // ensure cell is also visible from right scroll
4018 + if ((monthLeft + tableViewWidth) > monthDayLeft) {
4019 + // calculate cell exact visible width
4020 + let cellViewWidth = monthDayWidth;
4021 + if ((monthDayLeft + monthDayWidth) > (monthLeft + tableViewWidth)) {
4022 + // cell is cut from right scroll, count visible width
4023 + cellViewWidth = monthDayWidth - ((monthDayLeft + monthDayWidth) - (tableViewWidth + monthLeft));
4024 + } else if ((monthWidth + tableScrollLeft) > monthDayLeft) {
4025 + // cell is cut from left scroll, count visible width
4026 + cellViewWidth = monthDayWidth - ((monthWidth + tableScrollLeft) - monthDayLeft);
4027 + }
4028 + cellViewWidth = cellViewWidth > monthDayWidth || cellViewWidth < 1 ? 1 : cellViewWidth;
4029 +
4030 + // push cell details
4031 + visibleMonthDays.push({
4032 + wday: cell.querySelector('.vbo-overw-tablewday').innerText,
4033 + mday: cell.querySelector('.vbo-overw-tablemday').innerText,
4034 + ymd: cell.getAttribute('data-ymd'),
4035 + vwidth: cellViewWidth,
4036 + });
4037 + }
4038 + }
4039 + });
4040 +
4041 + return visibleMonthDays;
4042 +}
4043 +
4044 +/**
4045 + * Returns the information of the first visible month-day cell in a table head upon scrolling.
4046 + *
4047 + * @param object table the NodeList element representing a table.
4048 + *
4049 + * @return object|null
4050 + *
4051 + * @since 1.18.6 (J) - 1.8.6 (WP)
4052 + */
4053 +function vboOvervFirstVisibleTableHeadNode(table) {
4054 + // get the main table head element
4055 + let tableHeadElement = table.querySelector('tr.vboverviewtablerowone');
4056 +
4057 + // get month element of the given table
4058 + let monthElement = tableHeadElement.querySelector('.vbo-overview-month');
4059 + let monthLeft = monthElement.offsetLeft - 2;
4060 + let monthWidth = monthElement.offsetWidth;
4061 +
4062 + // get horizontal scroll left and table width from parent node
4063 + let tableScrollLeft = table.parentNode.scrollLeft;
4064 + let tableViewWidth = table.parentNode.offsetWidth;
4065 +
4066 + // attempt to identify the first visible month-day cell
4067 + let firstVisibleMonthDay = null;
4068 +
4069 + // scan all visible month-day elements
4070 + tableHeadElement.querySelectorAll('.bluedays:not(.vbo-overview-month)').forEach((cell) => {
4071 + if (firstVisibleMonthDay) {
4072 + return;
4073 + }
4074 +
4075 + // get cell offsets
4076 + let monthDayLeft = cell.offsetLeft;
4077 + let monthDayWidth = cell.offsetWidth;
4078 +
4079 + // ensure cell is visible from left scroll
4080 + if ((monthDayLeft + monthDayWidth) >= (monthLeft + monthWidth)) {
4081 + // ensure cell is also visible from right scroll
4082 + if ((monthLeft + tableViewWidth) > monthDayLeft) {
4083 + // visible cell found, gather the information
4084 + let ymd = cell.getAttribute('data-ymd');
4085 + let matchData = ymd.match(/^([0-9]{4})-(0?[1-9]|1[0-2])-[0-9]{2}$/);
4086 + if (!matchData) {
4087 + // unexpected cell information
4088 + return;
4089 + }
4090 +
4091 + // set first visible cell properties
4092 + firstVisibleMonthDay = {
4093 + ymd: ymd,
4094 + year: matchData[1],
4095 + mon: Number(matchData[2]),
4096 + no_scroll: !tableScrollLeft ? 1 : 0,
4097 + };
4098 + }
4099 + }
4100 + });
4101 +
4102 + return firstVisibleMonthDay;
4103 +}
4104 +
4105 +/**
4106 + * Fests
4107 + */
4108 +function vboRenderFests(day, daytitle) {
4109 + // compose fests information
4110 + var fests_html = '';
4111 + if (vboFests[day] && vboFests[day]['festinfo'] && vboFests[day]['festinfo'].length) {
4112 + for (var i = 0; i < vboFests[day]['festinfo'].length; i++) {
4113 + var fest = vboFests[day]['festinfo'][i];
4114 + fests_html += '<div class="vbo-overlay-fest-details">';
4115 + fests_html += ' <div class="vbo-fest-info">';
4116 + fests_html += ' <div class="vbo-fest-name">' + fest['trans_name'] + '</div>';
4117 + fests_html += ' <div class="vbo-fest-desc">' + fest['descr'].replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br />' + '$2') + '</div>';
4118 + fests_html += ' </div>';
4119 + fests_html += ' <div class="vbo-fest-cmds">';
4120 + fests_html += ' <button type="button" class="btn btn-danger" onclick="vboRemoveFest(\'' + day + '\', \'' + i + '\', \'' + fest['type'] + '\', this);"><?php VikBookingIcons::e('trash-alt', 'no-margin'); ?></button>';
4121 + fests_html += ' </div>';
4122 + fests_html += '</div>';
4123 + }
4124 + }
4125 +
4126 + // update ymd key for the selected date, useful for adding new fests
4127 + jQuery('.vbo-overlay-fests-addnew').attr('data-ymd', day);
4128 +
4129 + // set content and display modal
4130 + jQuery('.vbo-overlay-fests-list').html(fests_html);
4131 +
4132 + if (typeof daytitle !== 'undefined') {
4133 + // display modal only when we are not deleting a fest
4134 + var fests_modal = VBOCore.displayModal({
4135 + suffix: 'overv-mng-fests',
4136 + extra_class: 'vbo-modal-rounded vbo-modal-tall',
4137 + title: '<?php VikBookingIcons::e('star'); ?> <span>' + daytitle + '</span>',
4138 + footer_right: '<button type="button" class="btn btn-success" onclick="vboAddFest();">' + Joomla.JText._('VBSAVE') + '</button>',
4139 + dismiss_event: 'vbo-dismiss-modal-overv-mng-fests',
4140 + onDismiss: () => {
4141 + jQuery('.vbo-overv-mngfest-wrap').appendTo('.vbo-overv-mngfest-block');
4142 + },
4143 + });
4144 +
4145 + // set modal body
4146 + jQuery('.vbo-overv-mngfest-wrap').appendTo(fests_modal);
4147 + }
4148 +}
4149 +
4150 +function vboRemoveFest(day, index, fest_type, that) {
4151 + if (!confirm('<?php echo addslashes(JText::translate('VBDELCONFIRM')); ?>')) {
4152 + return false;
4153 + }
4154 + var elem = jQuery(that);
4155 + // make the AJAX request to the controller to remove this fest from the DB
4156 + VBOCore.doAjax(
4157 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=remove_fest'); ?>",
4158 + {
4159 + tmpl: "component",
4160 + dt: day,
4161 + ind: index,
4162 + type: fest_type
4163 + },
4164 + (res) => {
4165 + if (res.indexOf('e4j.ok') >= 0) {
4166 + // delete fest also from the json-decode array of objects
4167 + if (vboFests[day] && vboFests[day]['festinfo']) {
4168 + // use splice to remove the desired index from array, or delete would not make the length of the array change
4169 + vboFests[day]['festinfo'].splice(index, 1);
4170 + // re-build indexes of delete buttons, fundamental for removing the right index at next click
4171 + vboRenderFests(day);
4172 + if (!vboFests[day]['festinfo'].length) {
4173 + // delete also this date object from fests
4174 + delete vboFests[day];
4175 + // no more fests, remove the class for this date from all cells
4176 + jQuery('th.bluedays[data-ymd="'+day+'"]').removeClass('vbo-overv-festcell');
4177 + jQuery('td.notbusy[data-day="'+day+'"]').removeClass('vbo-overv-festcell');
4178 + jQuery('td.subnotbusy[data-day="'+day+'"]').removeClass('vbo-overv-festcell');
4179 + jQuery('td.busy[data-day="'+day+'"]').removeClass('vbo-overv-festcell');
4180 + jQuery('td.subroom-busy[data-day="'+day+'"]').removeClass('vbo-overv-festcell');
4181 + }
4182 + }
4183 + elem.closest('.vbo-overlay-fest-details').remove();
4184 + } else {
4185 + alert('Invalid response');
4186 + }
4187 + },
4188 + (err) => {
4189 + alert('Request failed');
4190 + }
4191 + );
4192 +}
4193 +
4194 +function vboAddFest() {
4195 + var ymd = jQuery('.vbo-overlay-fests-addnew').attr('data-ymd');
4196 + var fest_name = jQuery('#vbo-newfest-name').val();
4197 + var fest_descr = jQuery('#vbo-newfest-descr').val();
4198 + if (!fest_name || !fest_name.length) {
4199 + return false;
4200 + }
4201 + // make the AJAX request to the controller to add this fest to the DB
4202 + VBOCore.doAjax(
4203 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=add_fest'); ?>",
4204 + {
4205 + tmpl: "component",
4206 + dt: ymd,
4207 + type: "custom",
4208 + name: fest_name,
4209 + descr: fest_descr
4210 + },
4211 + (res) => {
4212 + // parse the JSON response that contains the fest object for the passed date
4213 + try {
4214 + var stored_fest = typeof res === 'string' ? JSON.parse(res) : res;
4215 + if (!vboFests.hasOwnProperty(stored_fest['dt'])) {
4216 + // we need to add the proper class to all cells to show that there is a fest
4217 + jQuery('th.bluedays[data-ymd="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
4218 + jQuery('td.notbusy[data-day="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
4219 + jQuery('td.subnotbusy[data-day="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
4220 + jQuery('td.busy[data-day="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
4221 + jQuery('td.subroom-busy[data-day="'+stored_fest['dt']+'"]').addClass('vbo-overv-festcell');
4222 + }
4223 + vboFests[stored_fest['dt']] = stored_fest;
4224 + // hide modal
4225 + VBOCore.emitEvent('vbo-dismiss-modal-overv-mng-fests');
4226 + // reset input fields
4227 + jQuery('#vbo-newfest-name').val('');
4228 + jQuery('#vbo-newfest-descr').val('');
4229 + } catch (e) {
4230 + alert('Invalid response');
4231 + return false;
4232 + }
4233 + },
4234 + (err) => {
4235 + alert('Request failed');
4236 + }
4237 + );
4238 +}
4239 +
4240 +/**
4241 + * Room-day notes
4242 + */
4243 +var rdaynote_icn_full = '<?php echo VikBookingIcons::i('sticky-note', 'vbo-roomdaynote-display'); ?>';
4244 +var rdaynote_icn_empty = '<?php echo VikBookingIcons::i('far fa-sticky-note', 'vbo-roomdaynote-display'); ?>';
4245 +function vboRenderRdayNotes(day, idroom, subunit, readymd) {
4246 + // compose fests information
4247 + var notes_html = '';
4248 + var keyid = day + '_' + idroom + '_' + subunit;
4249 + if (vboRdayNotes.hasOwnProperty(keyid) && vboRdayNotes[keyid]['info'] && vboRdayNotes[keyid]['info'].length) {
4250 + for (var i = 0; i < vboRdayNotes[keyid]['info'].length; i++) {
4251 + var note_data = vboRdayNotes[keyid]['info'][i];
4252 + notes_html += '<div class="vbo-overlay-fest-details vbo-modal-roomdaynotes-note-details">';
4253 + notes_html += ' <div class="vbo-fest-info vbo-modal-roomdaynotes-note-info">';
4254 + notes_html += ' <div class="vbo-fest-name vbo-modal-roomdaynotes-note-name">' + note_data['name'] + '</div>';
4255 + 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>';
4256 + notes_html += ' </div>';
4257 + notes_html += ' <div class="vbo-fest-cmds vbo-modal-roomdaynotes-note-cmds">';
4258 + 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>';
4259 + notes_html += ' </div>';
4260 + notes_html += '</div>';
4261 + }
4262 + }
4263 + // update attributes keys for the selected date, useful for adding new notes
4264 + jQuery('.vbo-modal-roomdaynotes-addnew').attr('data-ymd', day).attr('data-roomid', idroom).attr('data-subroomid', subunit);
4265 + if (readymd !== null) {
4266 + jQuery('.vbo-modal-roomdaynotes-addnew').attr('data-readymd', readymd);
4267 + jQuery('.vbo-newrdnote-dayto-val').text(readymd);
4268 + }
4269 + // set content and display modal
4270 + jQuery('.vbo-modal-roomdaynotes-list').html(notes_html);
4271 +}
4272 +
4273 +function vboAddRoomDayNote(that) {
4274 + var mainelem = jQuery('.vbo-modal-roomdaynotes-addnew');
4275 + var ymd = mainelem.attr('data-ymd');
4276 + var roomid = mainelem.attr('data-roomid');
4277 + var subroomid = mainelem.attr('data-subroomid');
4278 + var note_name = jQuery('#vbo-newrdnote-name').val();
4279 + var note_descr = jQuery('#vbo-newrdnote-descr').val();
4280 + var note_cdays = jQuery('#vbo-newrdnote-cdays').val();
4281 + if (!note_name.length && !note_descr.length) {
4282 + alert('Missing required fields');
4283 + return false;
4284 + }
4285 + // make the AJAX request to the controller to add this note to the DB
4286 + VBOCore.doAjax(
4287 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=add_roomdaynote'); ?>",
4288 + {
4289 + tmpl: "component",
4290 + dt: ymd,
4291 + idroom: roomid,
4292 + subunit: subroomid,
4293 + type: "custom",
4294 + name: note_name,
4295 + descr: note_descr,
4296 + cdays: note_cdays
4297 + },
4298 + (res) => {
4299 + // parse the JSON response that contains the note object for the passed date
4300 + try {
4301 + var stored_notes = typeof res === 'string' ? JSON.parse(res) : res;
4302 + for (var keyid in stored_notes) {
4303 + if (!stored_notes.hasOwnProperty(keyid)) {
4304 + continue;
4305 + }
4306 + if (!vboRdayNotes.hasOwnProperty(keyid) && jQuery('.vbo-roomdaynote-trigger[data-roomday="' + keyid + '"]').length) {
4307 + // we need to add the proper class to the cell for this note (if it's visible)
4308 + jQuery('.vbo-roomdaynote-trigger[data-roomday="' + keyid + '"]').parent('td').removeClass('vbo-roomdaynote-empty').addClass('vbo-roomdaynote-full').find('i').attr('class', rdaynote_icn_full);
4309 + }
4310 + // update global object with the new notes in any case
4311 + vboRdayNotes[keyid] = stored_notes[keyid];
4312 + }
4313 + // hide modal
4314 + VBOCore.emitEvent('vbo-dismiss-modal-overv-mng-rdaynotes');
4315 + // reset input fields
4316 + jQuery('#vbo-newrdnote-name').val('');
4317 + jQuery('#vbo-newrdnote-descr').val('');
4318 + jQuery('#vbo-newrdnote-cdays').val('0').trigger('change');
4319 + } catch (e) {
4320 + alert('Invalid response');
4321 + return false;
4322 + }
4323 + },
4324 + (err) => {
4325 + alert('Request failed');
4326 + }
4327 + );
4328 +}
4329 +
4330 +function vboRemoveRdayNote(index, day, idroom, subunit, note_type, that) {
4331 + if (!confirm('<?php echo addslashes(JText::translate('VBDELCONFIRM')); ?>')) {
4332 + return false;
4333 + }
4334 + var elem = jQuery(that);
4335 + // make the AJAX request to the controller to remove this note from the DB
4336 + VBOCore.doAjax(
4337 + "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=remove_roomdaynote'); ?>",
4338 + {
4339 + tmpl: "component",
4340 + dt: day,
4341 + idroom: idroom,
4342 + subunit: subunit,
4343 + ind: index,
4344 + type: note_type
4345 + },
4346 + (res) => {
4347 + if (res.indexOf('e4j.ok') >= 0) {
4348 + var keyid = day + '_' + idroom + '_' + subunit;
4349 + // delete note also from the json-decode array of objects
4350 + if (vboRdayNotes[keyid] && vboRdayNotes[keyid]['info']) {
4351 + // use splice to remove the desired index from array, or delete would not make the length of the array change
4352 + vboRdayNotes[keyid]['info'].splice(index, 1);
4353 + // re-build indexes of delete buttons, fundamental for removing the right index at next click
4354 + vboRenderRdayNotes(day, idroom, subunit, null);
4355 + if (!vboRdayNotes[keyid]['info'].length) {
4356 + // delete also this date object from notes
4357 + delete vboRdayNotes[keyid];
4358 + // no more notes, update the proper class attribute for this cell (should be visible)
4359 + if (jQuery('.vbo-roomdaynote-trigger[data-roomday="' + keyid + '"]').length) {
4360 + jQuery('.vbo-roomdaynote-trigger[data-roomday="' + keyid + '"]').parent('td').removeClass('vbo-roomdaynote-full').addClass('vbo-roomdaynote-empty').find('i').attr('class', rdaynote_icn_empty);
4361 + }
4362 + }
4363 + }
4364 + elem.closest('.vbo-modal-roomdaynotes-note-details').remove();
4365 + } else {
4366 + alert('Invalid response');
4367 + }
4368 + },
4369 + (err) => {
4370 + alert('Request failed');
4371 + }
4372 + );
4373 +}
4374 +
4375 +function vboRdayNoteCdaysCount() {
4376 + var cdays = parseInt(jQuery('#vbo-newrdnote-cdays').val());
4377 + var defymd = jQuery('.vbo-modal-roomdaynotes-addnew').attr('data-ymd');
4378 + var defreadymd = jQuery('.vbo-modal-roomdaynotes-addnew').attr('data-readymd');
4379 + defreadymd = !defreadymd || !defreadymd.length ? defymd : defreadymd;
4380 + if (isNaN(cdays) || cdays < 1) {
4381 + jQuery('.vbo-newrdnote-dayto-val').text(defreadymd);
4382 + return;
4383 + }
4384 + // calculate target (until) date
4385 + var targetdate = new Date(defymd);
4386 + targetdate.setDate(targetdate.getDate() + cdays);
4387 + var target_y = targetdate.getFullYear();
4388 + var target_m = targetdate.getMonth() + 1;
4389 + target_m = target_m < 10 ? '0' + target_m : target_m;
4390 + var target_d = targetdate.getDate();
4391 + target_d = target_d < 10 ? '0' + target_d : target_d;
4392 + // display target date
4393 + var display_target = target_y + '-' + target_m + '-' + target_d;
4394 + // check if we can get the "read ymd property"
4395 + if (jQuery('.bluedays[data-ymd="' + display_target + '"]').length) {
4396 + display_target = jQuery('.bluedays[data-ymd="' + display_target + '"]').attr('data-readymd');
4397 + }
4398 + jQuery('.vbo-newrdnote-dayto-val').text(display_target);
4399 +}
4400 +</script>
4401 +
4402 +<div class="vbo-orphans-overlay-block">
4403 + <a class="vbo-info-overlay-close" href="javascript: void(0);"></a>
4404 + <div class="vbo-info-overlay-content vbo-info-overlay-content-orphans">
4405 + <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>
4406 + <div class="vbo-info-overlay-scroll-content">
4407 + <div class="vbo-orphans-info-list"></div>
4408 + </div>
4409 + <div class="vbo-orphans-info-cmds">
4410 + <div class="vbo-orphans-info-cmd">
4411 + <button type="button" class="btn btn-success" onclick="javascript: hideVboDialogOverv(1);"><?php echo JText::translate('VBOBTNKEEPREMIND'); ?></button>
4412 + </div>
4413 + <div class="vbo-orphans-info-cmd">
4414 + <button type="button" class="btn btn-danger" onclick="javascript: hideVboDialogOverv(-1);"><?php echo JText::translate('VBOBTNDONTREMIND'); ?></button>
4415 + </div>
4416 + </div>
4417 + </div>
4418 +</div>
4419 +
4420 +<div class="vbo-overv-mngfest-block" style="display: none;">
4421 + <div class="vbo-overv-mngfest-wrap">
4422 + <div class="vbo-overlay-fests-list"></div>
4423 + <div class="vbo-overlay-fests-addnew" data-ymd="">
4424 + <h4><?php echo JText::translate('VBOADDCUSTOMFESTTODAY'); ?></h4>
4425 + <div class="vbo-overlay-fests-addnew-elem">
4426 + <label for="vbo-newfest-name"><?php echo JText::translate('VBPVIEWPLACESONE'); ?></label>
4427 + <input type="text" id="vbo-newfest-name" value="" />
4428 + </div>
4429 + <div class="vbo-overlay-fests-addnew-elem">
4430 + <label for="vbo-newfest-descr"><?php echo JText::translate('VBPLACEDESCR'); ?></label>
4431 + <textarea id="vbo-newfest-descr"></textarea>
4432 + </div>
4433 + </div>
4434 + </div>
4435 +</div>
4436 +
4437 +<div class="vbo-overv-mngroomdaynotes-block" style="display: none;">
4438 + <div class="vbo-overv-mngroomdaynotes-wrap">
4439 + <div class="vbo-modal-roomdaynotes-list"></div>
4440 + <div class="vbo-modal-roomdaynotes-addnew" data-readymd="" data-ymd="" data-roomid="" data-subroomid="">
4441 + <h4><?php echo JText::translate('VBOADDCUSTOMFESTTODAY'); ?></h4>
4442 + <div class="vbo-modal-roomdaynotes-addnew-elem">
4443 + <label for="vbo-newrdnote-name"><?php echo JText::translate('VBPVIEWPLACESONE'); ?></label>
4444 + <input type="text" id="vbo-newrdnote-name" value="" />
4445 + </div>
4446 + <div class="vbo-modal-roomdaynotes-addnew-elem">
4447 + <label for="vbo-newrdnote-descr"><?php echo JText::translate('VBPLACEDESCR'); ?></label>
4448 + <textarea id="vbo-newrdnote-descr"></textarea>
4449 + </div>
4450 + <div class="vbo-modal-roomdaynotes-addnew-elem">
4451 + <label for="vbo-newrdnote-cdays"><?php echo JText::translate('VBOCONSECUTIVEDAYS'); ?></label>
4452 + <input type="number" id="vbo-newrdnote-cdays" min="0" max="365" value="0" onchange="vboRdayNoteCdaysCount();" onkeyup="vboRdayNoteCdaysCount();" />
4453 + <span class="vbo-newrdnote-dayto">
4454 + <span class="vbo-newrdnote-dayto-lbl"><?php echo JText::translate('VBOUNTIL'); ?></span>
4455 + <span class="vbo-newrdnote-dayto-val"></span>
4456 + </span>
4457 + </div>
4458 + </div>
4459 + </div>
4460 +</div>