Lines 84-99
Link Here
|
84 |
[% END %] |
84 |
[% END %] |
85 |
[% END %] |
85 |
[% END %] |
86 |
|
86 |
|
87 |
[% SET aside = Koha.Preference('CircSidebar') ? 'circ-nav' : '' %] |
87 |
[% BLOCK curbside_pickups_enabled %] |
88 |
[% WRAPPER 'main-container.inc' aside=aside %] |
|
|
89 |
<h1>Curbside pickups</h1> |
88 |
<h1>Curbside pickups</h1> |
90 |
|
89 |
|
91 |
[% UNLESS policy.enabled %] |
|
|
92 |
<div class="alert alert-warning"> Curbside pickups are not enabled for your library. </div> |
93 |
[% INCLUDE 'intranet-bottom.inc' %] |
94 |
[% STOP %] |
95 |
[% END %] |
96 |
|
97 |
[% FOR m IN messages %] |
90 |
[% FOR m IN messages %] |
98 |
<div class="alert alert-[% m.type | html %]"> |
91 |
<div class="alert alert-[% m.type | html %]"> |
99 |
[% SWITCH m.code %] |
92 |
[% SWITCH m.code %] |
Lines 457-695
Link Here
|
457 |
[% END # /tab_panel %] |
450 |
[% END # /tab_panel %] |
458 |
[% END # /WRAPPER tab_panels %] |
451 |
[% END # /WRAPPER tab_panels %] |
459 |
[% END # /WRAPPER tabs %] |
452 |
[% END # /WRAPPER tabs %] |
460 |
[% END %] |
|
|
461 |
|
453 |
|
462 |
[% MACRO jsinclude BLOCK %] |
454 |
[% MACRO jsinclude BLOCK %] |
463 |
[% Asset.js("lib/dayjs/plugin/isSameOrAfter.js") | $raw %] |
455 |
[% Asset.js("lib/dayjs/plugin/isSameOrAfter.js") | $raw %] |
464 |
<script> |
456 |
<script> |
465 |
dayjs.extend(window.dayjs_plugin_isSameOrAfter); |
457 |
dayjs.extend(window.dayjs_plugin_isSameOrAfter); |
466 |
</script> |
458 |
</script> |
467 |
<script> |
459 |
<script> |
468 |
dayjs.extend(window.dayjs_plugin_customParseFormat); |
460 |
dayjs.extend(window.dayjs_plugin_customParseFormat); |
469 |
</script> |
461 |
</script> |
470 |
[% INCLUDE 'calendar.inc' %] |
462 |
[% INCLUDE 'calendar.inc' %] |
471 |
[% INCLUDE 'js-patron-format.inc' %] |
463 |
[% INCLUDE 'js-patron-format.inc' %] |
472 |
[% INCLUDE 'datatables.inc' %] |
464 |
[% INCLUDE 'datatables.inc' %] |
473 |
<script> |
465 |
<script> |
474 |
let pickups = [% To.json(curbside_pickups.unblessed) | $raw %]; |
466 |
let pickups = [% To.json(curbside_pickups.unblessed) | $raw %]; |
475 |
let policy = [% To.json(policy.unblessed) | $raw %]; |
467 |
let policy = [% To.json(policy.unblessed) | $raw %]; |
476 |
|
468 |
|
477 |
let existingPickupMoments = []; |
469 |
let existingPickupMoments = []; |
478 |
pickups.forEach(function(pickup){ |
470 |
pickups.forEach(function(pickup){ |
479 |
let scheduled_pickup_datetime = pickup.scheduled_pickup_datetime; |
471 |
let scheduled_pickup_datetime = pickup.scheduled_pickup_datetime; |
480 |
let pickupMoment = dayjs(scheduled_pickup_datetime); |
472 |
let pickupMoment = dayjs(scheduled_pickup_datetime); |
481 |
|
473 |
|
482 |
existingPickupMoments.push(pickupMoment); |
474 |
existingPickupMoments.push(pickupMoment); |
483 |
}); |
|
|
484 |
|
485 |
let opening_slots = [% To.json(policy.opening_slots.unblessed) | $raw %]; |
486 |
let slots_per_day = {}; |
487 |
opening_slots.forEach(function(slot){ |
488 |
let day = slot.day; |
489 |
if(!slots_per_day[day]) slots_per_day[day] = []; |
490 |
slots_per_day[day].push(slot); |
491 |
}); |
492 |
|
493 |
$(document).ready(function() { |
494 |
|
495 |
$('#schedule-pickup-tab').on('click', function() { |
496 |
$('#find-patron').focus(); |
497 |
}); |
475 |
}); |
498 |
|
476 |
|
499 |
[% IF tab == 'schedule-pickup_panel' %] |
477 |
let opening_slots = [% To.json(policy.opening_slots.unblessed) | $raw %]; |
500 |
$('#find-patron').focus(); |
478 |
let slots_per_day = {}; |
501 |
[% END %] |
479 |
opening_slots.forEach(function(slot){ |
|
|
480 |
let day = slot.day; |
481 |
if(!slots_per_day[day]) slots_per_day[day] = []; |
482 |
slots_per_day[day].push(slot); |
483 |
}); |
502 |
|
484 |
|
503 |
const pickup_date = document.querySelector("#pickup_date"); |
485 |
$(document).ready(function() { |
504 |
if ( pickup_date ) { |
|
|
505 |
const fp = pickup_date._flatpickr; |
506 |
fp.set('disable', [function(date) { |
507 |
return !slots_per_day.hasOwnProperty(date.getDay()); |
508 |
}]); |
509 |
} |
510 |
|
486 |
|
511 |
$("#pickup_date").on('change', function() { |
487 |
$('#schedule-pickup-tab').on('click', function() { |
|
|
488 |
$('#find-patron').focus(); |
489 |
}); |
512 |
|
490 |
|
513 |
$('#pickup-times').empty(); |
491 |
[% IF tab == 'schedule-pickup_panel' %] |
514 |
$('#schedule-pickup-button').prop( 'disabled', 1 ); |
492 |
$('#find-patron').focus(); |
|
|
493 |
[% END %] |
515 |
|
494 |
|
516 |
var currentDate = $(this).val(); |
495 |
const pickup_date = document.querySelector("#pickup_date"); |
|
|
496 |
if ( pickup_date ) { |
497 |
const fp = pickup_date._flatpickr; |
498 |
fp.set('disable', [function(date) { |
499 |
return !slots_per_day.hasOwnProperty(date.getDay()); |
500 |
}]); |
501 |
} |
517 |
|
502 |
|
518 |
let selectedDate = dayjs(currentDate); |
503 |
$("#pickup_date").on('change', function() { |
519 |
|
504 |
|
520 |
let pickupSlots = []; |
505 |
$('#pickup-times').empty(); |
521 |
let available_count = 0; |
506 |
$('#schedule-pickup-button').prop( 'disabled', 1 ); |
522 |
let dow = selectedDate.day(); // Sunday is 0 (at least for now) |
|
|
523 |
if (!slots_per_day[dow]){ |
524 |
$('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>"); |
525 |
return; |
526 |
} |
527 |
|
507 |
|
528 |
slots_per_day[dow].sort((a, b) => a.start_hour - b.start_hour).forEach(function(slot){ |
508 |
var currentDate = $(this).val(); |
529 |
let pickup_interval = policy.pickup_interval; |
509 |
|
530 |
if (!pickup_interval) { |
510 |
let selectedDate = dayjs(currentDate); |
|
|
511 |
|
512 |
let pickupSlots = []; |
513 |
let available_count = 0; |
514 |
let dow = selectedDate.day(); // Sunday is 0 (at least for now) |
515 |
if (!slots_per_day[dow]){ |
531 |
$('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>"); |
516 |
$('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>"); |
532 |
return; |
517 |
return; |
533 |
} |
518 |
} |
534 |
|
519 |
|
535 |
let listStartMoment = selectedDate.hour(slot.start_hour).minute(slot.start_minute); |
520 |
slots_per_day[dow].sort((a, b) => a.start_hour - b.start_hour).forEach(function(slot){ |
536 |
let listEndMoment = selectedDate.hour(slot.end_hour).minute(slot.end_minute); |
521 |
let pickup_interval = policy.pickup_interval; |
537 |
|
522 |
if (!pickup_interval) { |
538 |
let keep_going = true; |
523 |
$('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>"); |
539 |
let now = dayjs(); |
524 |
return; |
|
|
525 |
} |
540 |
|
526 |
|
541 |
// Initialize pickup slots starting at opening time |
527 |
let listStartMoment = selectedDate.hour(slot.start_hour).minute(slot.start_minute); |
542 |
let pickupIntervalStartMoment = listStartMoment; |
528 |
let listEndMoment = selectedDate.hour(slot.end_hour).minute(slot.end_minute); |
543 |
let pickupIntervalEndMoment = listStartMoment.add(pickup_interval, 'minutes'); |
|
|
544 |
while (keep_going) { |
545 |
let available = true; |
546 |
let display_slot = true |
547 |
|
529 |
|
548 |
if (pickupIntervalStartMoment.isBefore(now)) { |
530 |
let keep_going = true; |
549 |
// Slots in the past are unavailable |
531 |
let now = dayjs(); |
550 |
available = false; |
|
|
551 |
display_slot = false; |
552 |
} |
553 |
|
532 |
|
554 |
if (pickupIntervalEndMoment.isAfter(listEndMoment)) { |
533 |
// Initialize pickup slots starting at opening time |
555 |
// Slots after the end of pickup times for the day are unavailable |
534 |
let pickupIntervalStartMoment = listStartMoment; |
556 |
available = false; |
535 |
let pickupIntervalEndMoment = listStartMoment.add(pickup_interval, 'minutes'); |
557 |
} |
536 |
while (keep_going) { |
|
|
537 |
let available = true; |
538 |
let display_slot = true |
558 |
|
539 |
|
559 |
let pickups_scheduled = 0; |
540 |
if (pickupIntervalStartMoment.isBefore(now)) { |
560 |
existingPickupMoments.forEach(function(pickupMoment){ |
541 |
// Slots in the past are unavailable |
561 |
// An existing pickup time |
542 |
available = false; |
562 |
if (pickupMoment.isSameOrAfter(pickupIntervalStartMoment) && pickupMoment.isBefore(pickupIntervalEndMoment)) { |
543 |
display_slot = false; |
563 |
// This calculated pickup is in use by another scheduled pickup |
|
|
564 |
pickups_scheduled++; |
565 |
} |
544 |
} |
566 |
}); |
|
|
567 |
|
545 |
|
568 |
if (pickups_scheduled >= policy.patrons_per_interval) { |
546 |
if (pickupIntervalEndMoment.isAfter(listEndMoment)) { |
569 |
available = false; |
547 |
// Slots after the end of pickup times for the day are unavailable |
570 |
} |
548 |
available = false; |
|
|
549 |
} |
571 |
|
550 |
|
572 |
if ( display_slot ) { |
551 |
let pickups_scheduled = 0; |
573 |
pickupSlots.push( |
552 |
existingPickupMoments.forEach(function(pickupMoment){ |
574 |
{ |
553 |
// An existing pickup time |
575 |
"available": available, |
554 |
if (pickupMoment.isSameOrAfter(pickupIntervalStartMoment) && pickupMoment.isBefore(pickupIntervalEndMoment)) { |
576 |
"moment": pickupIntervalStartMoment, |
555 |
// This calculated pickup is in use by another scheduled pickup |
577 |
"pickups_scheduled": pickups_scheduled |
556 |
pickups_scheduled++; |
578 |
} |
557 |
} |
579 |
); |
558 |
}); |
580 |
} |
|
|
581 |
|
559 |
|
582 |
if ( available ) { |
560 |
if (pickups_scheduled >= policy.patrons_per_interval) { |
583 |
available_count++; |
561 |
available = false; |
584 |
} |
562 |
} |
585 |
|
563 |
|
586 |
pickupIntervalStartMoment = pickupIntervalEndMoment; |
564 |
if ( display_slot ) { |
587 |
pickupIntervalEndMoment = pickupIntervalStartMoment.add(pickup_interval, 'minutes'); |
565 |
pickupSlots.push( |
588 |
if (pickupIntervalEndMoment.isAfter(listEndMoment)) { |
566 |
{ |
589 |
// This latest slot is after the end of pickup times for the day, so we can stop |
567 |
"available": available, |
590 |
keep_going = false; |
568 |
"moment": pickupIntervalStartMoment, |
|
|
569 |
"pickups_scheduled": pickups_scheduled |
570 |
} |
571 |
); |
572 |
} |
573 |
|
574 |
if ( available ) { |
575 |
available_count++; |
576 |
} |
577 |
|
578 |
pickupIntervalStartMoment = pickupIntervalEndMoment; |
579 |
pickupIntervalEndMoment = pickupIntervalStartMoment.add(pickup_interval, 'minutes'); |
580 |
if (pickupIntervalEndMoment.isAfter(listEndMoment)) { |
581 |
// This latest slot is after the end of pickup times for the day, so we can stop |
582 |
keep_going = false; |
583 |
} |
591 |
} |
584 |
} |
|
|
585 |
}); |
586 |
|
587 |
for (let i = 0; i < pickupSlots.length; i++) { |
588 |
let pickupSlot = pickupSlots[i]; |
589 |
let optText = pickupSlot.moment.format("HH:mm"); |
590 |
let optValue = pickupSlot.moment.format("YYYY-MM-DD HH:mm:ss"); |
591 |
let pickups_scheduled = pickupSlot.pickups_scheduled; |
592 |
let disabled = pickupSlot.available ? "" : "disabled"; |
593 |
$("#pickup-times").append(`<span class="pickup_time"><input type="radio" id="slot_${i}" name="pickup_time" value="${optValue}" ${disabled} /> <label for="slot_${i}">${optText} (${pickups_scheduled})</label></span>`); |
592 |
} |
594 |
} |
593 |
}); |
|
|
594 |
|
595 |
|
595 |
for (let i = 0; i < pickupSlots.length; i++) { |
596 |
$('#pickup-times').show(); |
596 |
let pickupSlot = pickupSlots[i]; |
597 |
$('#schedule-pickup-button').prop( 'disabled', available_count <= 0 ); |
597 |
let optText = pickupSlot.moment.format("HH:mm"); |
598 |
}); |
598 |
let optValue = pickupSlot.moment.format("YYYY-MM-DD HH:mm:ss"); |
|
|
599 |
let pickups_scheduled = pickupSlot.pickups_scheduled; |
600 |
let disabled = pickupSlot.available ? "" : "disabled"; |
601 |
$("#pickup-times").append(`<span class="pickup_time"><input type="radio" id="slot_${i}" name="pickup_time" value="${optValue}" ${disabled} /> <label for="slot_${i}">${optText} (${pickups_scheduled})</label></span>`); |
602 |
} |
603 |
|
599 |
|
604 |
$('#pickup-times').show(); |
600 |
$("#create-pickup").on('submit', function(){ |
605 |
$('#schedule-pickup-button').prop( 'disabled', available_count <= 0 ); |
601 |
if ( ! $("input[type='radio']:checked").length ) { |
606 |
}); |
602 |
alert(_("Please select a date and a pickup time")) |
|
|
603 |
return false; |
604 |
} |
605 |
return true; |
606 |
}); |
607 |
|
607 |
|
608 |
$("#create-pickup").on('submit', function(){ |
608 |
if ( $("#find-patron").length ) { |
609 |
if ( ! $("input[type='radio']:checked").length ) { |
609 |
patron_autocomplete($("#find-patron"), { 'on-select-callback': function( event, ui ) { |
610 |
alert(_("Please select a date and a pickup time")) |
610 |
window.location.href = "/cgi-bin/koha/circ/curbside_pickups.pl?op=find-patron&borrowernumber=" + ui.item.patron_id; |
611 |
return false; |
611 |
return false; |
|
|
612 |
} |
613 |
}); |
612 |
} |
614 |
} |
613 |
return true; |
|
|
614 |
}); |
615 |
|
615 |
|
616 |
if ( $("#find-patron").length ) { |
616 |
$("#pickup-tabs a[data-bs-toggle='tab']").on("shown.bs.tab", function (e) { |
617 |
patron_autocomplete($("#find-patron"), { 'on-select-callback': function( event, ui ) { |
617 |
$("#current-tab").val($(this).attr('href').substring(1)); // Remove # |
618 |
window.location.href = "/cgi-bin/koha/circ/curbside_pickups.pl?op=find-patron&borrowernumber=" + ui.item.patron_id; |
618 |
}); |
619 |
return false; |
619 |
$("#auto_refresh,#refresh_delay").on("change", function(){ |
620 |
} |
620 |
set_interval_if_needed(); |
621 |
}); |
621 |
}); |
622 |
} |
|
|
623 |
|
622 |
|
624 |
$("#pickup-tabs a[data-bs-toggle='tab']").on("shown.bs.tab", function (e) { |
|
|
625 |
$("#current-tab").val($(this).attr('href').substring(1)); // Remove # |
626 |
}); |
627 |
$("#auto_refresh,#refresh_delay").on("change", function(){ |
628 |
set_interval_if_needed(); |
623 |
set_interval_if_needed(); |
629 |
}); |
|
|
630 |
|
624 |
|
631 |
set_interval_if_needed(); |
|
|
632 |
|
625 |
|
|
|
626 |
let dt_settings = []; |
627 |
dt_settings.push( [% TablesSettings.GetTableSettings('circ', 'curbside_pickups', 'to_be_stagedt', 'json') | $raw %] ); |
628 |
dt_settings.push( [% TablesSettings.GetTableSettings('circ', 'curbside_pickups', 'staged_and_readyt', 'json') | $raw %] ); |
629 |
dt_settings.push( [% TablesSettings.GetTableSettings('circ', 'curbside_pickups', 'patron_is_outsidet', 'json') | $raw %] ); |
630 |
dt_settings.push( [% TablesSettings.GetTableSettings('circ', 'curbside_pickups', 'delivered_todayt', 'json') | $raw %] ); |
631 |
|
632 |
let dt_tables = new Array("to_be_stagedt", "staged_and_readyt", "patron_is_outsidet", "delivered_todayt"); |
633 |
dt_tables.forEach(function( id, index ){ |
634 |
$("#" + id).kohaTable({ |
635 |
"autoWidth": false |
636 |
}, dt_settings[ index ] ); |
637 |
}); |
633 |
|
638 |
|
634 |
let dt_settings = []; |
|
|
635 |
dt_settings.push( [% TablesSettings.GetTableSettings('circ', 'curbside_pickups', 'to_be_stagedt', 'json') | $raw %] ); |
636 |
dt_settings.push( [% TablesSettings.GetTableSettings('circ', 'curbside_pickups', 'staged_and_readyt', 'json') | $raw %] ); |
637 |
dt_settings.push( [% TablesSettings.GetTableSettings('circ', 'curbside_pickups', 'patron_is_outsidet', 'json') | $raw %] ); |
638 |
dt_settings.push( [% TablesSettings.GetTableSettings('circ', 'curbside_pickups', 'delivered_todayt', 'json') | $raw %] ); |
639 |
|
639 |
|
640 |
let dt_tables = new Array("to_be_stagedt", "staged_and_readyt", "patron_is_outsidet", "delivered_todayt"); |
|
|
641 |
dt_tables.forEach(function( id, index ){ |
642 |
$("#" + id).kohaTable({ |
643 |
"autoWidth": false |
644 |
}, dt_settings[ index ] ); |
645 |
}); |
640 |
}); |
646 |
|
641 |
|
|
|
642 |
let refresh_interval_id = 0; |
643 |
let countdown_interval_id = 0; |
644 |
function set_interval(refresh_delay_ms){ |
645 |
clear_intervals(); |
646 |
let next_refresh = new Date(); |
647 |
next_refresh.setSeconds(next_refresh.getSeconds() + refresh_delay_ms / 1000); |
648 |
|
649 |
countdown_interval_id = setInterval(function() { |
650 |
const now = new Date().getTime(); |
651 |
const seconds = Math.floor(( next_refresh - now + 1 ) / 1000); |
652 |
if ( seconds > 0 ) { |
653 |
$("#refresh_info").text(_("Refresh in %s seconds").format(seconds)); |
654 |
} else { |
655 |
$("#refresh_info").text(""); // In case something is going wrong |
656 |
} |
657 |
}, 1000); |
647 |
|
658 |
|
648 |
}); |
659 |
setInterval(function() { |
|
|
660 |
$(".refresh-form:visible").submit(); |
661 |
}, refresh_delay_ms); |
662 |
} |
663 |
function clear_intervals(){ |
664 |
if (refresh_interval_id) { |
665 |
clearInterval(refresh_interval_id); |
666 |
refresh_interval_id = 0; |
667 |
} |
668 |
if (countdown_interval_id) { |
669 |
clearInterval(countdown_interval_id); |
670 |
countdown_interval_id = 0; |
671 |
} |
672 |
} |
649 |
|
673 |
|
650 |
let refresh_interval_id = 0; |
674 |
function set_interval_if_needed(){ |
651 |
let countdown_interval_id = 0; |
675 |
const refresh_delay = $("#refresh_delay").val(); |
652 |
function set_interval(refresh_delay_ms){ |
676 |
const auto_refresh = $("#auto_refresh").is(":checked"); |
653 |
clear_intervals(); |
|
|
654 |
let next_refresh = new Date(); |
655 |
next_refresh.setSeconds(next_refresh.getSeconds() + refresh_delay_ms / 1000); |
656 |
|
677 |
|
657 |
countdown_interval_id = setInterval(function() { |
678 |
if (auto_refresh && refresh_delay){ |
658 |
const now = new Date().getTime(); |
679 |
set_interval(refresh_delay * 1000); |
659 |
const seconds = Math.floor(( next_refresh - now + 1 ) / 1000); |
|
|
660 |
if ( seconds > 0 ) { |
661 |
$("#refresh_info").text(_("Refresh in %s seconds").format(seconds)); |
662 |
} else { |
680 |
} else { |
663 |
$("#refresh_info").text(""); // In case something is going wrong |
681 |
clear_intervals(); |
664 |
} |
682 |
} |
665 |
}, 1000); |
|
|
666 |
|
667 |
setInterval(function() { |
668 |
$(".refresh-form:visible").submit(); |
669 |
}, refresh_delay_ms); |
670 |
} |
671 |
function clear_intervals(){ |
672 |
if (refresh_interval_id) { |
673 |
clearInterval(refresh_interval_id); |
674 |
refresh_interval_id = 0; |
675 |
} |
683 |
} |
676 |
if (countdown_interval_id) { |
684 |
</script> |
677 |
clearInterval(countdown_interval_id); |
685 |
[% END %] |
678 |
countdown_interval_id = 0; |
686 |
[% END # /BLOCK enabled %] |
679 |
} |
|
|
680 |
} |
681 |
|
687 |
|
682 |
function set_interval_if_needed(){ |
688 |
[% BLOCK curbside_pickups_disabled %] |
683 |
const refresh_delay = $("#refresh_delay").val(); |
689 |
<h1>Curbside pickups</h1> |
684 |
const auto_refresh = $("#auto_refresh").is(":checked"); |
690 |
<div class="alert alert-warning">Curbside pickups are not enabled for your library.</div> |
|
|
691 |
[% END # /BLOCK disabled %] |
685 |
|
692 |
|
686 |
if (auto_refresh && refresh_delay){ |
693 |
[% SET aside = Koha.Preference('CircSidebar') ? 'circ-nav' : '' %] |
687 |
set_interval(refresh_delay * 1000); |
694 |
[% WRAPPER 'main-container.inc' aside=aside %] |
688 |
} else { |
695 |
[% IF !disabled_for_branch %] |
689 |
clear_intervals(); |
696 |
[% PROCESS curbside_pickups_enabled %] |
690 |
} |
697 |
[% ELSE %] |
691 |
} |
698 |
[% PROCESS curbside_pickups_disabled %] |
692 |
</script> |
699 |
[% END %] |
693 |
[% END %] |
700 |
[% END # /WRAPPER %] |
694 |
|
701 |
|
695 |
[% INCLUDE 'intranet-bottom.inc' %] |
702 |
[% INCLUDE 'intranet-bottom.inc' %] |
696 |
- |
|
|