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

month.php in VikBooking Hotel Booking Engine & PMS 1.8.8, at admin/layouts/taskmanager/calendar/month.php

843 lines 37.9 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 /**
15 * Obtain vars from arguments received in the layout file.
16 *
17 * @var array $data The data for rendering the tasks of a given month within a calendar.
18 */
19 extract($displayData);
20
21 // access the task manager object
22 $taskManager = VBOFactory::getTaskManager();
23
24 // get the task area IDs
25 $areaIds = [];
26
27 if (is_array($data['area_ids'] ?? null)) {
28 $areaIds = $data['area_ids'];
29 }
30
31 // get the filters for rendering the calendar tasks
32 $filters = (array) ($data['filters'] ?? []);
33
34 // determine the month to load
35 $fromYmd = $filters['calendar_month'] ?? date('Y-m-01');
36 $toYmd = date('Y-m-t', strtotime($fromYmd));
37 if (($filters['dates'] ?? null) && !($filters['calendar_month'] ?? null)) {
38 // when no calendar month filter set, use the current dates filter
39 list($fromDt, $toDt) = VBOTaskModelTask::getInstance()->getFilterDatesInterval((string) $filters['dates'], $local = true, $sql = false);
40 if ($fromDt && $toDt) {
41 $fromYmd = date('Y-m-01', strtotime($fromDt));
42 $toYmd = date('Y-m-t', strtotime($toDt));
43 }
44 }
45
46 // build the pool of month tasks
47 $monthTasks = [];
48
49 if ($areaIds) {
50 // save current filters in the user state
51 JFactory::getApplication()->setUserState('vbo.tm.filters', $filters);
52
53 // load tasks according to filters, by always forcing/injecting the area IDs and the dates
54 foreach (VBOTaskModelTask::getInstance()->filterItems(array_merge($filters, ['id_areas' => $areaIds, 'dates' => $fromYmd . ':' . $toYmd]), 0, 0) as $taskRecord) {
55 // wrap task record into a registry
56 $task = VBOTaskTaskregistry::getInstance((array) $taskRecord);
57
58 // get task due date in Y-m-d format
59 $due_date_key = $task->getDueDate($local = true, $format = 'Y-m-d');
60
61 if ($due_date_key) {
62 // start a container for this date
63 $monthTasks[$due_date_key] = $monthTasks[$due_date_key] ?? [];
64
65 // push task registry for this day
66 $monthTasks[$due_date_key][] = $task;
67 }
68 }
69 }
70
71 // obtain the current month information
72 $monthDate = getdate(strtotime($fromYmd));
73 $monthIter = $monthDate;
74
75 // get navigation dates
76 $todayYmd = date('Y-m-d');
77 $monthBack = date('Y-m-01', strtotime('-1 month', $monthDate[0]));
78 $monthNext = date('Y-m-01', strtotime('+1 month', $monthDate[0]));
79 $monthToday = date('Y-m-01');
80
81 // build week-day indexes
82 $firstwday = (int) VikBooking::getFirstWeekDay();
83 $days_indexes = [];
84 $wday_labels = [
85 JText::translate('VBSUN'),
86 JText::translate('VBMON'),
87 JText::translate('VBTUE'),
88 JText::translate('VBWED'),
89 JText::translate('VBTHU'),
90 JText::translate('VBFRI'),
91 JText::translate('VBSAT'),
92 ];
93
94 for ($d = 0; $d < 7; $d++) {
95 $days_indexes[$d] = (6-($firstwday-$d)+1)%7;
96 }
97
98 ?>
99
100 <div class="vbo-tm-calendar-wrap">
101 <div class="vbo-tm-calendar-head">
102 <div class="vbo-tm-calendar-info">
103 <span class="vbo-tm-calendar-month-name"><?php echo VikBooking::sayMonth($monthDate['mon']); ?></span>
104 <span class="vbo-tm-calendar-month-year"><?php echo $monthDate['year']; ?></span>
105 </div>
106
107 <div class="vbo-tm-calendar-nav">
108 <div class="vbo-tm-calendar-nav-btns">
109 <span class="vbo-tm-calendar-nav-btn vbo-tm-calendar-nav-back" data-month="<?php echo $monthBack; ?>"><?php VikBookingIcons::e('chevron-left'); ?></span>
110 <span class="vbo-tm-calendar-nav-btn vbo-tm-calendar-nav-today" data-month="<?php echo $monthToday; ?>"><?php echo JText::translate('VBTODAY'); ?></span>
111 <span class="vbo-tm-calendar-nav-btn vbo-tm-calendar-nav-next" data-month="<?php echo $monthNext; ?>"><?php VikBookingIcons::e('chevron-right'); ?></span>
112 </div>
113 </div>
114 </div>
115
116 <div class="vbo-tm-calendar-month-container">
117
118 <div class="vbo-tm-calendar-month-row vbo-tm-calendar-month-weekdays">
119 <?php
120 // display days of week
121 for ($d = 0; $d < 7; $d++) {
122 $d_ind = ($d + $firstwday) < 7 ? ($d + $firstwday) : ($d + $firstwday - 7);
123 ?>
124 <div class="vbo-tm-calendar-month-day vbo-tm-calendar-month-weekday">
125 <span><?php echo $wday_labels[$d_ind]; ?></span>
126 </div>
127 <?php
128 }
129 ?>
130 </div>
131
132 <div class="vbo-tm-calendar-month-row">
133 <?php
134 // start month-day counter
135 $d_count = 0;
136
137 // display the initial empty week-days, if any
138 for ($i = 0, $n = $days_indexes[$monthDate['wday']]; $i < $n; $i++, $d_count++) {
139 ?>
140 <div class="vbo-tm-calendar-month-day vbo-tm-calendar-month-day-empty"></div>
141 <?php
142 }
143
144 // loop through all dates of the current month
145 while ($monthIter['mon'] == $monthDate['mon']) {
146 if ($d_count > 6) {
147 // week is over, reset counter and start a new row
148 $d_count = 0;
149 echo '</div>' . "\n" . '<div class="vbo-tm-calendar-month-row">' . "\n";
150 }
151
152 // build date key
153 $date_key = date('Y-m-d', $monthIter[0]);
154
155 ?>
156 <div class="vbo-tm-calendar-month-day<?php echo $date_key == $todayYmd ? ' vbo-tm-calendar-month-today' : ''; ?>" data-date="<?php echo $date_key; ?>">
157 <span class="vbo-tm-calendar-mday"><?php echo $monthIter['mday']; ?></span>
158 <?php
159 if ($monthTasks[$date_key] ?? []) {
160 ?>
161 <div class="vbo-tm-calendar-month-day-tasks">
162 <?php
163 foreach ($monthTasks[$date_key] as $taskIndex => $task) {
164 // get task status
165 $statusColor = '';
166 $statusName = '';
167 if ($taskManager->statusTypeExists($task->getStatus())) {
168 $status = $taskManager->getStatusTypeInstance($task->getStatus());
169 $statusColor = $status->getColor();
170 $statusName = $status->getName();
171 }
172
173 // get task due time
174 $dueTime = $task->getDueDate($local = true, $format = 'H:i:s');
175
176 // get task assignee details
177 $assignees = $task->getAssigneeDetails();
178 ?>
179 <div class="vbo-tm-calendar-month-day-task vbo-tm-color <?php echo $statusColor ?: 'gray'; ?> <?php echo $statusName ? 'vbo-tooltip vbo-tooltip-top' : ''; ?>" data-task-id="<?php echo $task->getID(); ?>" data-area-id="<?php echo $task->getAreaID(); ?>" data-due-time="<?php echo $dueTime; ?>" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $statusName); ?>">
180 <div class="vbo-tm-calendar-month-day-task-wrap">
181 <div class="vbo-tm-calendar-task-title">
182 <?php echo $task->getTitle(); ?>
183
184 <?php if ($task->get('hasUnreadMessages', false)): ?>
185 <span class="unread-message-dot">
186 <?php VikBookingIcons::e('comment'); ?>
187 </span>
188 <?php endif; ?>
189 </div>
190 <?php
191 if ($assignees) {
192 ?>
193 <span class="vbo-tm-calendar-task-assignees">
194 <?php
195 foreach ($assignees as $operator) {
196 ?>
197 <span class="vbo-tm-calendar-task-assignee vbo-tm-task-assignee">
198 <span class="vbo-tm-calendar-task-assignee-avatar vbo-tm-task-assignee-avatar" title="<?php echo JHtml::fetch('esc_attr', $operator['name']); ?>">
199 <?php
200 if (!empty($operator['img_uri'])) {
201 ?>
202 <img src="<?php echo $operator['img_uri']; ?>" alt="<?php echo JHtml::fetch('esc_attr', $operator['initials']); ?>" decoding="async" loading="lazy" />
203 <?php
204 } else {
205 ?>
206 <span><?php echo $operator['initials']; ?></span>
207 <?php
208 }
209 ?>
210 </span>
211 </span>
212 <?php
213 }
214 ?>
215 </span>
216 <?php
217 }
218 ?>
219 </div>
220 </div>
221 <?php
222 if ($taskIndex > 1 && $exceeding = (count($monthTasks[$date_key]) - ++$taskIndex)) {
223 // we display at most 3 tasks per day
224 ?>
225 <div class="vbo-tm-calendar-month-day-task vbo-tm-calendar-month-day-more" data-date="<?php echo $date_key; ?>">
226 <span><?php echo sprintf('+%d %s', $exceeding, strtolower($exceeding > 1 ? JText::translate('VBO_TASKS') : JText::translate('VBO_TASK'))); ?></span>
227 </div>
228 <?php
229
230 // break the execution of the tasks for this day
231 break;
232 }
233 }
234 ?>
235 </div>
236 <?php
237 }
238 ?>
239 </div>
240 <?php
241 // increase month-day counter
242 $d_count++;
243
244 // go to next day
245 $monthIter = getdate(mktime(0, 0, 0, $monthIter['mon'], $monthIter['mday'] + 1, $monthIter['year']));
246 }
247
248 // display the ending empty week-days, if any
249 for ($i = $d_count; $i <= 6; $i++) {
250 ?>
251 <div class="vbo-tm-calendar-month-day vbo-tm-calendar-month-day-empty"></div>
252 <?php
253 }
254 ?>
255 </div>
256
257 </div>
258 </div>
259
260 <script type="text/javascript">
261 jQuery(function($) {
262
263 let newTaskButtons = [];
264
265 newTaskButtons.push({
266 text: <?php echo json_encode(JText::translate('VBO_NEW_TASK')); ?>,
267 class: 'btngroup',
268 disabled: true,
269 });
270
271 vboTmAllAreas.forEach((area) => {
272 // push area button
273 newTaskButtons.push({
274 areaId: area.id,
275 class: 'vbo-context-menu-entry-secondary',
276 icon: '<?php echo VikBookingIcons::i('plus-circle'); ?>',
277 text: area.name,
278 action: (root, event) => {
279 VBOCore.emitEvent('vbo-tm-newtask-trigger', {
280 areaId: area.id,
281 filters: Object.assign({}, vboTmFilters, {
282 calendar_type: 'day',
283 calendar_day: $(root).attr('data-date'),
284 }),
285 });
286 },
287 });
288 });
289
290 newTaskButtons[newTaskButtons.length - 1].separator = true;
291
292 newTaskButtons.push({
293 text: <?php echo json_encode(JText::translate('VBO_SEE_ALL')); ?>,
294 class: 'vbo-context-menu-entry-secondary',
295 icon: '<?php echo VikBookingIcons::i('eye'); ?>',
296 visible: (root, config) => {
297 return $(root).find('.vbo-tm-calendar-month-day-task').length ? true : false;
298 },
299 action: (root, event) => {
300 VBOCore.emitEvent('vbo-tm-apply-filters', {
301 filters: {
302 calendar_type: 'day',
303 calendar_day: $(root).attr('data-date'),
304 },
305 });
306 }
307 });
308
309 /**
310 * Register listeners for month navigation buttons.
311 */
312 document
313 .querySelectorAll('.vbo-tm-calendar-nav-btn')
314 .forEach((nav) => {
315 // get the navigation month
316 const month = nav.getAttribute('data-month');
317
318 nav.addEventListener('click', () => {
319 VBOCore.emitEvent('vbo-tm-apply-filters', {
320 filters: {
321 calendar_month: month,
322 },
323 });
324 });
325 });
326
327 /**
328 * Register listeners for day calendar type.
329 */
330 document
331 .querySelectorAll('.vbo-tm-calendar-month-day[data-date]')
332 .forEach((day) => {
333 // get the navigation month
334 const ymd = day.getAttribute('data-date');
335
336 day
337 .querySelector('.vbo-tm-calendar-mday')
338 .addEventListener('click', (event) => {
339 event.preventDefault();
340 event.stopPropagation();
341
342 VBOCore.emitEvent('vbo-tm-apply-filters', {
343 filters: {
344 calendar_type: 'day',
345 calendar_day: ymd,
346 },
347 });
348 });
349
350 $(day).vboContextMenu({
351 buttons: newTaskButtons,
352 class: 'vbo-dropdown-cxmenu',
353 });
354 });
355
356 /**
357 * Store the original task day-cell container for drag & drop.
358 */
359 let taskOriginDnD = null;
360
361 /**
362 * Register listeners for editing an existing task.
363 */
364 document
365 .querySelectorAll('.vbo-tm-calendar-month-day-task')
366 .forEach((taskElement) => {
367 if (taskElement.clickListener) {
368 // listener added already
369 return;
370 }
371
372 // get the clicked task and area IDs
373 const taskId = taskElement.getAttribute('data-task-id');
374 const areaId = taskElement.getAttribute('data-area-id');
375 const date = taskElement.getAttribute('data-date');
376
377 if ((!taskId || !areaId) && !date) {
378 // missing task details
379 return;
380 }
381
382 if (!taskId && !areaId && date) {
383 // see more day tasks
384 taskElement.addEventListener('click', (event) => {
385 event.preventDefault();
386 event.stopPropagation();
387
388 VBOCore.emitEvent('vbo-tm-apply-filters', {
389 filters: {
390 calendar_type: 'day',
391 calendar_day: date,
392 },
393 });
394 });
395
396 // abort
397 return;
398 }
399
400 // register click event
401 taskElement.addEventListener('click', (event) => {
402 event.preventDefault();
403 event.stopPropagation();
404
405 // access the container day-cell
406 let taskDayCell = taskElement.closest('.vbo-tm-calendar-month-day');
407
408 // check if event is in the future (after today)
409 let isFuture = true;
410 if (taskDayCell && new Date(taskDayCell.getAttribute('data-date') + ' 00:00:00') <= new Date()) {
411 // task is in the past
412 isFuture = false;
413 }
414
415 // define the modal delete button
416 let delete_btn = $('<button></button>')
417 .attr('type', 'button')
418 .addClass('btn btn-danger')
419 .text(<?php echo json_encode(JText::translate('VBELIMINA')); ?>)
420 .on('click', function() {
421 if (!confirm(<?php echo json_encode(JText::translate('VBDELCONFIRM')); ?>)) {
422 return false;
423 }
424
425 // disable button to prevent double submissions
426 let submit_btn = $(this);
427 submit_btn.prop('disabled', true);
428
429 // start loading animation
430 VBOCore.emitEvent('vbo-tm-edittask-loading');
431
432 // make the request
433 VBOCore.doAjax(
434 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.deleteTask'); ?>",
435 {
436 data: {
437 id: taskId,
438 },
439 },
440 (resp) => {
441 // trigger filters-changed event on success
442 VBOCore.emitEvent('vbo-tm-filters-changed', {
443 filters: vboTmFilters,
444 });
445
446 // dismiss the modal
447 VBOCore.emitEvent('vbo-tm-edittask-dismiss');
448 },
449 (error) => {
450 // display error message
451 alert(error.responseText);
452
453 // re-enable submit button
454 submit_btn.prop('disabled', false);
455
456 // stop loading
457 VBOCore.emitEvent('vbo-tm-edittask-loading');
458 }
459 );
460 });
461
462 // define the modal save button
463 let save_btn = $('<button></button>')
464 .attr('type', 'button')
465 .addClass('btn btn-success')
466 .text(<?php echo json_encode(JText::translate('VBSAVE')); ?>)
467 .on('click', function() {
468 // disable button to prevent double submissions
469 let submit_btn = $(this);
470 submit_btn.prop('disabled', true);
471
472 // start loading animation
473 VBOCore.emitEvent('vbo-tm-edittask-loading');
474
475 // get form data
476 const taskForm = new FormData(document.querySelector('#vbo-tm-task-manage-form'));
477
478 // build query parameters for the request
479 let qpRequest = new URLSearchParams(taskForm);
480
481 // make sure the request always includes the assignees query parameter, even if the list is empty
482 if (!qpRequest.has('data[assignees][]')) {
483 qpRequest.append('data[assignees][]', []);
484 }
485
486 // make sure the request always includes the tags query parameter, even if the list is empty
487 if (!qpRequest.has('data[tags][]')) {
488 qpRequest.append('data[tags][]', []);
489 }
490
491 // make the request
492 VBOCore.doAjax(
493 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.updateTask'); ?>",
494 qpRequest.toString(),
495 (resp) => {
496 // trigger filters-changed event on success
497 VBOCore.emitEvent('vbo-tm-filters-changed', {
498 filters: vboTmFilters,
499 });
500
501 // dismiss the modal
502 VBOCore.emitEvent('vbo-tm-edittask-dismiss');
503 },
504 (error) => {
505 // display error message
506 alert(error.responseText);
507
508 // re-enable submit button
509 submit_btn.prop('disabled', false);
510
511 // stop loading
512 VBOCore.emitEvent('vbo-tm-edittask-loading');
513 }
514 );
515 });
516
517 let save_btn_content = null;
518
519 if (!isFuture) {
520 // define the "actions" button to allow repeating the task
521 let actions_btn = $('<button></button>')
522 .attr('type', 'button')
523 .addClass('btn btn-success')
524 .html('<?php VikBookingIcons::e('ellipsis-h'); ?>');
525
526 // define the callback to repeat the task
527 const repeatTaskFunc = function(interval) {
528 // start loading animation
529 VBOCore.emitEvent('vbo-tm-edittask-loading');
530
531 // make the request
532 VBOCore.doAjax(
533 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.repeatTask'); ?>",
534 {
535 task_id: taskId,
536 interval: interval,
537 },
538 (resp) => {
539 // trigger filters-changed event on success
540 VBOCore.emitEvent('vbo-tm-filters-changed', {
541 filters: vboTmFilters,
542 });
543
544 // dismiss the modal
545 VBOCore.emitEvent('vbo-tm-edittask-dismiss');
546 },
547 (error) => {
548 // display error message
549 alert(error.responseText);
550
551 // stop loading
552 VBOCore.emitEvent('vbo-tm-edittask-loading');
553 }
554 );
555 };
556
557 // define the context menu for the actions button
558 $(actions_btn).vboContextMenu({
559 placement: 'top-left',
560 buttons: [
561 {
562 class: 'btngroup',
563 text: <?php echo json_encode(JText::translate('VBO_REPEAT')); ?>,
564 disabled: true,
565 },
566 {
567 class: 'vbo-context-menu-entry-secondary',
568 text: <?php echo json_encode(JText::translate('VBOTOMORROW')); ?>,
569 action: (root, event) => {
570 repeatTaskFunc('1 day');
571 },
572 },
573 {
574 class: 'vbo-context-menu-entry-secondary',
575 text: <?php echo json_encode(JText::sprintf('VBO_REL_EXP_FUTURE', sprintf('2 %s', strtolower(JText::translate('VBCONFIGSEARCHPMAXDATEDAYS'))))); ?>,
576 separator: true,
577 action: (root, event) => {
578 repeatTaskFunc('2 days');
579 },
580 },
581 {
582 class: 'vbo-context-menu-entry-secondary',
583 text: <?php echo json_encode(JText::sprintf('VBO_REL_EXP_FUTURE', sprintf('1 %s', strtolower(JText::translate('VBOWEEK'))))); ?>,
584 action: (root, event) => {
585 repeatTaskFunc('1 week');
586 },
587 },
588 {
589 class: 'vbo-context-menu-entry-secondary',
590 text: <?php echo json_encode(JText::sprintf('VBO_REL_EXP_FUTURE', sprintf('2 %s', strtolower(JText::translate('VBCONFIGSEARCHPMAXDATEWEEKS'))))); ?>,
591 separator: true,
592 action: (root, event) => {
593 repeatTaskFunc('2 weeks');
594 },
595 },
596 {
597 class: 'vbo-context-menu-entry-secondary',
598 text: <?php echo json_encode(JText::sprintf('VBO_REL_EXP_FUTURE', sprintf('1 %s', strtolower(JText::translate('VBPVIEWRESTRICTIONSTWO'))))); ?>,
599 action: (root, event) => {
600 repeatTaskFunc('1 month');
601 },
602 },
603 {
604 class: 'vbo-context-menu-entry-secondary',
605 text: <?php echo json_encode(JText::translate('VBPVIEWORDERSONE')); ?>,
606 action: (root, event) => {
607 // display dialog modal
608 let dialogBody = document.createElement('div');
609 dialogBody.classList.add('vbo-admin-container', 'vbo-admin-container-full', 'vbo-admin-container-compact');
610 let wrap = document.createElement('div');
611 wrap.classList.add('vbo-params-wrap');
612 let paramContainer = document.createElement('div');
613 paramContainer.classList.add('vbo-param-container');
614 let paramLabel = document.createElement('div');
615 paramLabel.classList.add('vbo-param-label');
616 paramLabel.innerText = <?php echo json_encode(JText::translate('VBPVIEWORDERSONE')); ?>;
617 let paramSetting = document.createElement('div');
618 paramSetting.classList.add('vbo-param-setting');
619 let dateControl = document.createElement('input');
620 dateControl.setAttribute('type', 'date');
621 dateControl.setAttribute('min', new Date().getFullYear() + '-01-01');
622
623 // build dialog body
624 paramSetting.append(dateControl);
625 paramContainer.append(paramLabel);
626 paramContainer.append(paramSetting);
627 wrap.append(paramContainer);
628 dialogBody.append(wrap);
629
630 // build dialog buttons
631 let dialogCancelBtn = document.createElement('button');
632 dialogCancelBtn.setAttribute('type', 'button');
633 dialogCancelBtn.classList.add('btn');
634 dialogCancelBtn.innerText = <?php echo json_encode(JText::translate('VBANNULLA')); ?>;
635 dialogCancelBtn.addEventListener('click', () => {
636 VBOCore.emitEvent('vbo-tm-repeat-task-date-dismiss');
637 });
638 let dialogApplyBtn = document.createElement('button');
639 dialogApplyBtn.setAttribute('type', 'button');
640 dialogApplyBtn.classList.add('btn', 'btn-primary');
641 dialogApplyBtn.innerText = <?php echo json_encode(JText::translate('VBAPPLY')); ?>;
642 dialogApplyBtn.addEventListener('click', () => {
643 if (dateControl.value) {
644 VBOCore.emitEvent('vbo-tm-repeat-task-date-dismiss');
645 repeatTaskFunc(dateControl.value);
646 }
647 });
648
649 // render modal
650 VBOCore.displayModal({
651 suffix: 'vbo-tm-repeat-task-date',
652 extra_class: 'vbo-modal-rounded vbo-modal-dialog',
653 title: <?php echo json_encode(JText::translate('VBO_REPEAT')); ?>,
654 body_prepend: true,
655 lock_scroll: true,
656 body: dialogBody,
657 footer_left: dialogCancelBtn,
658 footer_right: dialogApplyBtn,
659 dismiss_event: 'vbo-tm-repeat-task-date-dismiss',
660 });
661 },
662 },
663 ],
664 });
665
666 // define modal button to include the actions button
667 save_btn_content = $('<div></div>')
668 .addClass('btn-group')
669 .addClass('vbo-context-menu-btn-group')
670 .append(save_btn)
671 .append(actions_btn);
672 }
673
674 // display modal
675 let modalBody = VBOCore.displayModal({
676 suffix: 'tm_edittask_modal',
677 title: <?php echo json_encode(JText::translate('VBO_TASK')); ?> + ' #' + taskId,
678 extra_class: 'vbo-modal-rounded vbo-modal-taller vbo-modal-large',
679 body_prepend: true,
680 lock_scroll: true,
681 escape_dismiss: false,
682 footer_left: delete_btn,
683 footer_right: save_btn_content || save_btn,
684 loading_event: 'vbo-tm-edittask-loading',
685 dismiss_event: 'vbo-tm-edittask-dismiss',
686 });
687
688 // start loading animation
689 VBOCore.emitEvent('vbo-tm-edittask-loading');
690
691 // make the request
692 VBOCore.doAjax(
693 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.renderLayout'); ?>",
694 {
695 type: 'tasks.managetask',
696 data: {
697 task_id: taskId,
698 area_id: areaId,
699 form_id: 'vbo-tm-task-manage-form',
700 },
701 },
702 (resp) => {
703 // stop loading
704 VBOCore.emitEvent('vbo-tm-edittask-loading');
705
706 try {
707 // decode the response (if needed), and append the content to the modal body
708 let obj_res = typeof resp === 'string' ? JSON.parse(resp) : resp;
709 modalBody.append(obj_res['html']);
710 } catch (err) {
711 console.error('Error decoding the response', err, resp);
712 }
713 },
714 (error) => {
715 // display error message
716 alert(error.responseText);
717
718 // stop loading
719 VBOCore.emitEvent('vbo-tm-edittask-loading');
720 }
721 );
722 });
723
724 // set draggable attribute
725 taskElement.setAttribute('draggable', true);
726
727 // register dragstart event
728 taskElement.addEventListener('dragstart', (event) => {
729 // set task ID within data transfer
730 event.dataTransfer.setData('text/plain', taskId);
731
732 // update task day-cell origin
733 taskOriginDnD = taskElement.closest('.vbo-tm-calendar-month-day');
734
735 // add dragging class
736 setTimeout(() => taskElement.classList.add('dragging'), 0);
737 });
738
739 // register dragend event
740 taskElement.addEventListener('dragend', (event) => {
741 // remove dragging class
742 taskElement.classList.remove('dragging');
743
744 // ensure the drop zone is accepted, or snap back to original position
745 let taskDayCell = taskElement.closest('.vbo-tm-calendar-month-day');
746 if (!taskDayCell && taskOriginDnD) {
747 // snap back
748 taskOriginDnD.prepend(taskElement);
749 }
750 });
751
752 // turn flag on for listener set
753 taskElement.clickListener = true;
754 });
755
756 /**
757 * Register listeners for dropping a task inside a day-cell.
758 */
759 document
760 .querySelectorAll('.vbo-tm-calendar-month-day:not(.vbo-tm-calendar-month-day-empty)')
761 .forEach((dayCell) => {
762 // register dragover event
763 dayCell.addEventListener('dragover', (event) => {
764 if (dayCell != taskOriginDnD) {
765 event.preventDefault();
766 dayCell.classList.add('drag-over');
767 }
768 });
769
770 // register dragleave event
771 dayCell.addEventListener('dragleave', (event) => {
772 dayCell.classList.remove('drag-over');
773 });
774
775 // register the drop event
776 dayCell.addEventListener('drop', (event) => {
777 event.preventDefault();
778 dayCell.classList.remove('drag-over');
779
780 // access the dropped task ID within data transfer
781 const taskId = event.dataTransfer.getData('text/plain');
782 if (!taskId) {
783 // abort
784 return;
785 }
786
787 // access the dropped task element
788 const taskElement = document.querySelector('.vbo-tm-calendar-month-day-task[data-task-id="' + taskId + '"]');
789 if (!taskElement) {
790 // abort
791 return;
792 }
793
794 // access the task container and target for this day
795 let taskContainer = dayCell.querySelector('.vbo-tm-calendar-month-day-tasks');
796
797 if (!taskContainer) {
798 // create element on an empty day-cell
799 taskContainer = document.createElement('div');
800 taskContainer.classList.add('vbo-tm-calendar-month-day-tasks');
801
802 // append new task container
803 dayCell.append(taskContainer);
804 }
805
806 if (!taskOriginDnD || dayCell.getAttribute('data-date') == taskOriginDnD.getAttribute('data-date')) {
807 // abort for invalid target cell
808 return;
809 }
810
811 // add updating class
812 taskElement.classList.add('updating');
813
814 // move task onto the dropped cell
815 taskContainer.prepend(taskElement);
816
817 // make the request
818 VBOCore.doAjax(
819 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.updateTask'); ?>",
820 {
821 data: {
822 id: taskId,
823 dueon: dayCell.getAttribute('data-date') + ' ' + taskElement.getAttribute('data-due-time'),
824 },
825 },
826 (resp) => {
827 // get rid of the upating class upon completion
828 // taskElement.classList.remove('updating');
829 },
830 (error) => {
831 // display error message
832 alert(error.responseText);
833
834 // snap back
835 taskOriginDnD.prepend(taskElement);
836 }
837 );
838 });
839 });
840
841 });
842 </script>
843