PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
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 1.7.3 All 36 releases
vikbooking / admin / views / taskmanager / tmpl / default_calendar.php

default_calendar.php in VikBooking Hotel Booking Engine & PMS trunk, at admin/views/taskmanager/tmpl/default_calendar.php

454 lines 14.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2025 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 // collect a list of active area IDs
15 $activeAreaIds = [];
16
17 foreach ($this->visibleAreas as $areaRecord) {
18 // wrap the area record into a task area object
19 $taskArea = VBOTaskArea::getInstance((array) $areaRecord);
20
21 // push active area ID
22 $activeAreaIds[] = $taskArea->getID();
23 }
24
25 // determine the current calendar type
26 $allowedTypes = [
27 'month',
28 'day',
29 ];
30 $calendarType = $this->filters['calendar_type'] ?? $allowedTypes[0];
31 $calendarType = in_array($calendarType, $allowedTypes) ? $calendarType : $allowedTypes[0];
32
33 // make sure to set the proper calendar type filter
34 $this->filters['calendar_type'] = $calendarType;
35
36 ?>
37
38 <div class="vbo-tm-calendar-tasks-wrap">
39 <?php
40 // display task manager calendar layout type with NO task areas
41 $layout_data = [
42 'data' => [
43 'filters' => $this->filters,
44 // empty list for task area IDs to load just an empty calendar
45 'area_ids' => [],
46 ],
47 ];
48
49 echo JLayoutHelper::render('taskmanager.calendar.' . $calendarType, $layout_data);
50 ?>
51 </div>
52
53 <script type="text/javascript">
54 /**
55 * Register the global active area IDs.
56 */
57 const vboTmCalendarAreaIds = <?php echo json_encode($activeAreaIds); ?>;
58
59 /**
60 * Register function to activate the month loading animation.
61 */
62 const vboTmCalendarSetMonthLoading = (filters) => {
63 let month_info = document.querySelector('.vbo-tm-calendar-info');
64
65 if (!month_info) {
66 return;
67 }
68
69 month_info.innerHTML = '<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>';
70 };
71
72 /**
73 * Register function to load the area tasks.
74 */
75 const vboTmCalendarLoadTasks = (filters) => {
76 // activate loading
77 vboTmCalendarSetMonthLoading();
78
79 // detect the calendar layout type to load
80 let calendarType = filters && filters?.calendar_type ? filters.calendar_type : '<?php echo $calendarType; ?>';
81
82 // access the tasks wrapper
83 let tasksWrapper = document.querySelector('.vbo-tm-calendar-tasks-wrap');
84
85 // make the request for loading the tasks
86 VBOCore.doAjax(
87 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.renderLayout'); ?>",
88 {
89 type: 'calendar.' + calendarType,
90 data: {
91 area_ids: vboTmCalendarAreaIds,
92 filters: filters || vboTmFilters || {},
93 },
94 },
95 (resp) => {
96 try {
97 // decode the response (if needed)
98 let obj_res = typeof resp === 'string' ? JSON.parse(resp) : resp;
99
100 // set the loading result
101 jQuery(tasksWrapper).html(obj_res['html']);
102
103 // dispatch event for TM contents loaded
104 VBOCore.emitEvent('vbo-tm-contents-loaded', {
105 element: tasksWrapper,
106 });
107 } catch (err) {
108 console.error('Error decoding the response', err, resp);
109 }
110 },
111 (error) => {
112 // display error message
113 alert(error.responseText);
114 }
115 );
116 };
117
118 jQuery(function() {
119
120 /**
121 * Load calendar tasks upon page loading.
122 */
123 vboTmCalendarLoadTasks();
124
125 /**
126 * Register to the event for rendering the tasks of an area.
127 */
128 document.addEventListener('vbo-tm-area-render-task', (e) => {
129 if (!e || !e?.detail?.areaId) {
130 return;
131 }
132
133 let areaId = parseInt(e.detail.areaId);
134
135 if (!vboTmCalendarAreaIds.includes(areaId)) {
136 // push new area ID
137 vboTmCalendarAreaIds.push(areaId);
138 }
139
140 // reload tasks
141 vboTmCalendarLoadTasks();
142 // toggle area visibility
143 vboTmToggleAreaDisplay(areaId, 1);
144 });
145
146 /**
147 * Register to the event for hiding the tasks of an area.
148 */
149 document.addEventListener('vbo-tm-area-hide-task', (e) => {
150 if (!e || !e?.detail?.areaId) {
151 return;
152 }
153
154 let areaId = parseInt(e.detail.areaId);
155
156 let index = vboTmCalendarAreaIds.indexOf(areaId);
157 if (index >= 0) {
158 // remove the requested area ID
159 vboTmCalendarAreaIds.splice(index, 1);
160 }
161
162 // reload tasks
163 vboTmCalendarLoadTasks();
164 // toggle area visibility
165 vboTmToggleAreaDisplay(areaId, 0);
166 });
167
168 /**
169 * Register listener for the filters changed event.
170 */
171 document.addEventListener('vbo-tm-filters-changed', (e) => {
172 // obtain the global filters
173 let filters = e?.detail?.filters;
174
175 // re-render tasks calendar
176 vboTmCalendarLoadTasks(filters);
177 });
178
179 });
180
181 (function($) {
182 'use strict';
183
184 /**
185 * Defines the ratio to scale the size of the elements.
186 *
187 * @var float
188 */
189 let TABLE_SCALE_RATIO = 2.5;
190
191 /**
192 * Checks whether the specified intervals collide.
193 *
194 * @param Date start1 The initial date time of the first interval.
195 * @param Date end1 The ending date time of the first interval.
196 * @param Date start2 The initial date time of the second interval.
197 * @param Date end1 The ending date time of the second interval.
198 *
199 * @return boolean
200 */
201 const checkIntersection = (start1, end1, start2, end2) => {
202 return (start1 <= start2 && start2 < end1)
203 || (start1 < end2 && end2 <= end1)
204 || (start2 < start1 && end1 < end2);
205 }
206
207 /**
208 * Proxy used to speed up the usage of checkIntersection by passing 2 valid events.
209 *
210 * @param object event1 The first event.
211 * @param object event2 The second event.
212 *
213 * @return boolean
214 */
215 const checkEventsIntersection = (event1, event2) => {
216 return checkIntersection(
217 new Date(event1.start),
218 new Date(event1.end),
219 new Date(event2.start),
220 new Date(event2.end)
221 );
222 }
223
224 /**
225 * Returns a list containing all the events that collide with the specified one.
226 *
227 * @param object event An object holding the event details.
228 * @param mixed level An optional threshold to obtain only the
229 * events on the left of the specified one.
230 *
231 * @return array
232 */
233 const countIntersections = (event, level) => {
234 let list = [];
235
236 $('.vbo-tm-calendar-day-timeline-rows').find('.event').each(function() {
237 let event2 = $(this).data('event');
238
239 if (checkEventsIntersection(event, event2)) {
240 if (typeof level === 'undefined' || parseInt($(this).data('index')) < level) {
241 list.push(this);
242 }
243 }
244 });
245
246 return list;
247 }
248
249 /**
250 * Recursively adjusts the location and size of all the events that
251 * collide with the specified one.
252 *
253 * @param object event An object holding the event details.
254 *
255 * @return void
256 */
257 const fixSiblingsCount = (event) => {
258 let did = [];
259
260 // recursive fix
261 _fixSiblingsCount(event, did);
262 }
263
264 /**
265 * Recursively adjusts the location and size of all the events that
266 * collide with the specified one.
267 * @visibility protected
268 *
269 * @param object event An object holding the event details.
270 * @param array did An array containing all the events that
271 * have been already fixed, just to avoid
272 * increasing them more than once.
273 *
274 * @return void
275 */
276 const _fixSiblingsCount = (event, did) => {
277 let index = parseInt($(event).data('index'));
278
279 let intersections = countIntersections($(event).data('event'), index);
280
281 if (intersections.length) {
282 intersections.forEach((e) => {
283 let found = false;
284
285 // make sure we didn't already fetch this event
286 did.forEach((ei) => {
287 found = found || $(e).is(ei);
288 });
289
290 if (!found) {
291 // get counters
292 let tmp = parseInt($(e).data('siblings'));
293 let index = parseInt($(e).data('index'));
294
295 // adjust counter, size and position
296 $(e).data('siblings', tmp + 1);
297 $(e).css('width', 'calc(calc(100% / ' + (tmp + 2) + ') - 2px)');
298 $(e).css('left', 'calc(calc(calc(100% / ' + (tmp + 2) + ') * ' + (index) + ') + 2px)');
299
300 // flag event as already adjusted
301 did.push(e);
302
303 // recursively adjust the colliding events
304 _fixSiblingsCount(e, did);
305 }
306 });
307 }
308 }
309
310 /**
311 * Adds the specified event into the calendar.
312 *
313 * @param object data An object holding the event details.
314 *
315 * @return void
316 */
317 const addCalendarEvent = (data) => {
318 // search the row matching the hour of the event
319 const hourRow = $('.vbo-tm-calendar-day-timeline-rows').find('.vbo-tm-calendar-day-timeline-row[data-hour="' + data.hour + '"]');
320
321 if (!hourRow.length) {
322 return false;
323 }
324
325 // create event
326 const event = $(data.html).addClass('event');
327 delete data.html;
328
329 // event.attr('id', 'event-' + data.id);
330 // event.attr('data-order-id', data.id);
331 event.data('event', data);
332
333 if (data.duration <= 15) {
334 event.addClass('xsmall-block');
335 } else if (data.duration < 30) {
336 event.addClass('small-block');
337 }
338
339 // calculate event offset from top
340 let offset = (data.hour * 60 + data.min) * TABLE_SCALE_RATIO;
341 // calculate the threshold that cannot be exceeded
342 let ceil = 1440 * TABLE_SCALE_RATIO;
343
344 // make sure the height doesn't exceed the ceil
345 let height = Math.min(data.duration * TABLE_SCALE_RATIO, ceil - offset) - 1;
346
347 // vertically locate and resize the event box
348 event.css('top', (data.min * TABLE_SCALE_RATIO) + 'px');
349 event.css('height', height + 'px');
350
351 // set color according to the selected service
352 // let color = ('' + data.service_color).replace(/^#/, '');
353
354 // event.css('background-color', '#' + color + '80');
355 // event.css('border-left-color', '#' + color);
356
357 // count number of events that intersect the appointment
358 let intersections = countIntersections(data);
359
360 let count = 0;
361
362 // find the highest index position among the colliding events
363 intersections.forEach((e) => {
364 count = Math.max(count, parseInt($(e).data('index')) + 1);
365 });
366
367 // init siblings counter and index with the amount previously found
368 event.data('siblings', count);
369 event.data('index', count);
370
371 // recursively adjust the counter of any other colliding event
372 fixSiblingsCount(event);
373
374 // locate and size the event before attaching it
375 event.css('width', 'calc(calc(100% / ' + (count + 1) + ') - 2px)');
376 event.css('left', 'calc(calc(calc(100% / ' + (count + 1) + ') * ' + (count) + ') + 2px)');
377
378 // attach event to calendar
379 hourRow.find('.vbo-tm-calendar-day-tasks').append(event);
380 }
381
382 /**
383 * Configures the calendar by adding all the specified events.
384 *
385 * @param array events A list of events to append.
386 *
387 * @return void
388 */
389 window.taskManagerSetupCalendar = (events) => {
390 $('.vbo-tm-calendar-day-tasks').html('');
391
392 if (!events.length) {
393 // do nothing
394 return;
395 }
396
397 // init events
398 events.forEach((event) => {
399 event.intersections = [];
400 });
401
402 // scan conflicts between times
403 for (var i = 0; i < events.length - 1; i++) {
404 for (var j = i + 1; j < events.length; j++) {
405 let a = events[i];
406 let b = events[j];
407
408 if (checkEventsIntersection(a, b)) {
409 a.intersections.push(b);
410 b.intersections.push(a);
411 }
412 }
413 }
414
415 // sort events by conflicts and ascending time
416 events.sort((a, b) => {
417 let diff = a.intersections.length - b.intersections.length;
418
419 if (diff == 0) {
420 // same intersections, sort by check-in time
421 diff = (a.hour * 60 + a.min) - (b.hour * 60 + b.min);
422 }
423
424 return diff;
425 });
426
427 // attach events to calendar one by one
428 events.forEach((event) => {
429 addCalendarEvent(event);
430 });
431
432 let bounds = [24, -1];
433
434 // calculate the minimum and the maximum hours that hold at least a task
435 events.forEach((event) => {
436 bounds[0] = Math.min(bounds[0], event.hour);
437 bounds[1] = Math.max(bounds[1], event.hour + Math.ceil(event.duration / 60));
438 });
439
440 // hide all the hours before and after the calculated bounds
441 $('.vbo-tm-calendar-day-timeline-row[data-hour]').each(function() {
442 const hour = parseInt($(this).data('hour'));
443
444 if (hour < bounds[0] - 1 || hour > bounds[1] + 1) {
445 $(this).hide();
446 } else {
447 $(this).show();
448 }
449 });
450 }
451 })(jQuery);
452
453 </script>
454