Koha/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-curbside-pickups.tt
Owen Leonard 869dc05acc
Bug 34849: Use template wrapper for breadcrumbs: OPAC part 1
This patch updates several OPAC templates so that they
use a new WRAPPER for displaying breadcrumbs.

The patch contains a minor CSS update, so you will have to rebuild the
OPAC CSS for testings.

Apply the patch and log in to the OPAC. Test each of the following pages
and their variations. Breadcrumbs should look correct, and each link
should be correct:

- User summary
- Charges
- Personal details
  - Submit changes
- Consents (with PrivacyPolicyConsent enabled)
- Tags
- Change password
  - Submit password change
- Search history
  - Test when logged in and when not logged in
- Checkout history
- Privacy
- Holds history
- Recalls history
- Messaging (with  EnhancedMessagingPreferences
  and EnhancedMessagingPreferencesOPAC enabled).
- Lists
  - View private list
  - View public list
  - Edit list
  - Share list
    - Submit invitation
  - Create list
  - View list contents
- Routing lists (with RoutingSerials enabled)
- Ask for a discharge (with useDischarge enabled)
- ILL requests
- Curbside pickups
- Error page: Navigate to a non-existent page

Signed-off-by: Philip Orr <philip.orr@lmscloud.de>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
2023-10-18 10:25:58 -03:00

488 lines
24 KiB
Text

[% USE raw %]
[% USE To %]
[% USE Koha %]
[% USE KohaDates %]
[% USE Branches %]
[% USE AdditionalContents %]
[% SET OpacNav = AdditionalContents.get( location => "OpacNav", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %]
[% SET OpacNavBottom = AdditionalContents.get( location => "OpacNavBottom", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %]
[% INCLUDE 'doc-head-open.inc' %]
<title>Your curbside pickups &rsaquo; [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
[% FILTER collapse %]
<style>
.pickup_time input[type='radio'] {
display: none;
}
.pickup_time {
display: inline-block;
}
#pickup-times {
display: flex;
flex-wrap: wrap;
}
#pickup-time-slots {
max-width: 80%;
}
fieldset.rows .pickup_time label {
background: #e6e6e6 linear-gradient(180deg,#f0f0f0,#e6e6e6);
border: 1px solid #b3b3b3;
border-radius: 3px;
cursor: pointer;
float: none;
font-weight: normal;
padding: .2em .1em;
text-align: center;
width: 6rem;
}
fieldset.rows .pickup_time label:hover {
background: #e6e6e6 linear-gradient(180deg,#F7F7F7,#F0F0F0);
}
fieldset.rows .pickup_time label::before {
font-family: "Font Awesome 6 Free";
color: #AAA;
content: "\f1db";
display: inline-block;
padding-right: .5em;
}
fieldset.rows .pickup_time input:checked + label {
background: #5cb85c linear-gradient(180deg,#5cb85c,#4cae4c);
border: 1px solid #548e54;
color: #FFF;
}
fieldset.rows .pickup_time input:checked + label::before {
font-family: "Font Awesome 6 Free";
color: #FFF;
content: "\f05d";
display: inline-block;
padding-right: .5em;
}
fieldset.rows .pickup_time input:disabled + label {
background: #F0F0F0 none;
border: 1px solid #E6E6E6;
color: #6c6c6c;
}
fieldset.rows .pickup_time input:disabled + label:hover {
cursor: not-allowed;
}
fieldset.rows .pickup_time input:disabled + label::before {
content: "";
display: inline-block;
}
#existing-pickup-warning {
color: #c00;
display: inline-block;
}
.pickups_available {
font-size: 90%;
}
.pickups_available::before {
content: "(";
}
.pickups_available::after {
content: ")";
}
</style>
[% END %]
[% INCLUDE 'doc-head-close.inc' %]
[% BLOCK cssinclude %][% END %]
</head>
[% INCLUDE 'bodytag.inc' bodyid='opac-curside-pickups' bodyclass='scrollto' %]
[% INCLUDE 'masthead.inc' %]
<div class="main">
[% WRAPPER breadcrumbs %]
[% WRAPPER breadcrumb_item %]
<a href="/cgi-bin/koha/opac-user.pl">[% INCLUDE 'patron-title.inc' patron = logged_in_user %]</a>
[% END %]
[% WRAPPER breadcrumb_item bc_active= 1 %]
<span>Curbside pickups</span>
[% END %]
[% END #/ WRAPPER breadcrumbs %]
<div class="container-fluid">
<div class="row">
<div class="col col-lg-2 order-2 order-lg-1">
<div id="navigation">
[% INCLUDE 'navigation.inc' IsPatronPage=1 %]
</div>
</div>
<div class="col-md-12 col-lg-10 order-1">
<div id='pickupdetails' class="maincontent">
<h2>Curbside pickups</h2>
[% FOR m IN messages %]
[% IF m.type == "error" %]
<div class="alert alert-warning">
[% ELSE %]
<div class="alert alert-info">
[% END %]
[% SWITCH m.code %]
[% CASE 'not_enabled' %]
<span>The curbside pickup feature is not enabled for this library.</span>
[% CASE 'library_is_closed' %]
<span>Cannot create a curbside pickup for this day, it is a holiday.</span>
[% CASE 'no_waiting_holds' %]
<span>This patron does not have waiting holds.</span>
[% CASE 'too_many_pickups' %]
<span>You already have a scheduled pickup for this library.</span>
[% CASE 'no_matching_slots' %]
<span>Wrong slot selected.</span>
[% CASE 'no_more_pickups_available' %]
<span>There are no more pickups available for this slot. Please choose another one.</span>
[% CASE 'cannot_checkout' %]
<span>Unable to check the items out to [% INCLUDE 'patron-title.inc' patron=m.patron %]</span>
[% CASE 'library_notified' %]
<span>The library has been notified of your arrival.</span>
[% CASE %]
<span>[% m.code | html %]</span>
[% END %]
</div>
[% END %]
<div id="opac-pickups-views" class="toptabs">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item" role="presentation" id="tab-user-pickups">
[% IF patron_curbside_pickups.count %]
<a href="#user-pickups" class="nav-link active" aria-controls="user-pickups" aria-selected="true" role="tab" data-toggle="tab">Your pickups</a>
[% ELSE %]
<a href="#user-pickups" class="nav-link" aria-controls="user-pickups" role="tab" data-toggle="tab">Your pickups</a>
[% END %]
</li>
[% IF policies.count %]
<li class="nav-item" role="presentation" id="tab-user-schedule-pickup">
[% IF patron_curbside_pickups.count %]
<a href="#user-schedule-pickup" class="nav-link" aria-controls="user-schedule-pickup" role="tab" data-toggle="tab">Schedule a pickup</a>
[% ELSE %]
<a href="#user-schedule-pickup" class="nav-link active" aria-controls="user-schedule-pickup" aria-selected="true" role="tab" data-toggle="tab">Schedule a pickup</a>
[% END %]
</li>
[% END %]
</ul>
<div class="tab-content">
[% IF patron_curbside_pickups.count %]
<div role="tabpanel" class="tab-pane active" id="user-pickups" aria-labelledby="tab-user-pickups">
[% ELSE %]
<div role="tabpanel" class="tab-pane" id="user-pickups" aria-labelledby="tab-user-pickups">
[% END %]
[% IF patron_curbside_pickups.count %]
<table id="pickups-table" class="table table-striped">
<thead>
<tr>
<th>Pickup library</td>
<th>Schedule</th>
<th>Notes</th>
<th>Actions</th>
<th></th>
</tr>
</thead>
<tbody>
[% FOR p IN patron_curbside_pickups %]
<tr>
<td>[% Branches.GetName(p.branchcode) | html %]</td>
<td>[% p.scheduled_pickup_datetime | $KohaDates with_hours => 1 %]</td>
<td>[% p.notes | html %]</td>
<td>
<form method="post">
<input type="hidden" name="op" value="arrival-alert" />
<input type="hidden" name="pickup_id" value="[% p.id | html %]" />
[% IF ! p.staged_datetime || p.arrival_datetime %]
<button class="btn disabled" disabled href="#" >
[% ELSE %]
<button type="submit" class="btn" href="#" >
[% END %]
<i class="fa fa-bell" aria-hidden="true"></i> Alert staff of your arrival
</button>
</form>
<p>
<form method="post">
<input type="hidden" name="op" value="cancel-pickup" />
<input type="hidden" name="pickup_id" value="[% p.id | html %]" />
[% IF p.delivered_datetime %]
<button class="btn disabled" disabled href="#" >
[% ELSE %]
<button type="submit" class="btn" href="#" >
[% END %]
<i class="fa fa-ban" aria-hidden="true"></i> Cancel this pickup</button>
</form>
</td>
<td></td>
</tr>
[% END %]
</tbody>
</table>
[% ELSE %]
<div>No curbside pickups.</div>
[% END %]
</div>
[% IF policies.count %]
[% IF patron_curbside_pickups.count %]
<div role="tabpanel" class="tab-pane" id="user-schedule-pickup" aria-labelledby="tab-user-schedule-pickup">
[% ELSE %]
<div role="tabpanel" class="tab-pane active" id="user-schedule-pickup" aria-labelledby="tab-user-schedule-pickup">
[% END %]
<form id="create-pickup" method="post">
<fieldset class="rows">
<ol>
<li>
<label for="pickup-branch" class="required">Pickup library:</label>
<select name="pickup_branch" id="pickup-branch" required="required">
<option value="">Select a library</option>
[% FOR p IN policies %]
<option value="[% p.branchcode | html %]">[% Branches.GetName(p.branchcode) | html %]</option>
[% END %]
</select>
<span id="existing-pickup-warning" class="required" style="display: none;">You already have a pickup scheduled for this library.</span>
<div class="required_label required">Required</div>
</li>
<li id="pickup_date_item">
<label for="pickup_date">Pickup date:</label>
<input name="pickup_date" type="text" class="flatpickr" id="pickup-date" required="required" data-flatpickr-futureinclusive="true" />
<span class="required">Required</span>
</li>
<li id="pickup-times"></li>
<li id="pickup_notes_item">
<label for="notes">Notes:</label>
<input name="notes" id="notes" />
</li>
</ol>
</fieldset>
<fieldset class="action">
<input type="hidden" name="op" value="create-pickup" />
<input type="submit" id="schedule-pickup-button" class="btn btn-default" disabled value="Schedule pickup" />
</fieldset>
</form>
</div>
[% END %]
</div>
</div>
</div>
</div> <!-- / .col-lg-10 -->
</div> <!-- / .row -->
</div> <!-- / .container-fluid -->
</div> <!-- / .main -->
[% INCLUDE 'opac-bottom.inc' %]
[% BLOCK jsinclude %]
[% Asset.js("lib/dayjs/dayjs.min.js") | $raw %]
[% Asset.js("lib/dayjs/plugin/isSameOrAfter.js") | $raw %]
[% Asset.js("lib/dayjs/plugin/customParseFormat.js") | $raw %]
<script>dayjs.extend(window.dayjs_plugin_isSameOrAfter)</script>
<script>dayjs.extend(window.dayjs_plugin_customParseFormat)</script>
[% INCLUDE 'calendar.inc' %]
[% INCLUDE 'datatables.inc' %]
<script>
[% SET pickup_exists_in = [] %]
[% FOR p IN patron_curbside_pickups %]
[% UNLESS p.delivered_by %]
[% pickup_exists_in.push(p.branchcode) %]
[% END %]
[% END %]
let pickup_exists_in = [% To.json(pickup_exists_in.unique()) | $raw %];
let pickups = [% To.json(curbside_pickups.unblessed) | $raw %];
let policies = [% To.json(policies.unblessed) | $raw %];
policies = policies.reduce((map, e) => {
map[e.branchcode] = e;
return map;
}, {});
let can_schedule_at = {};
[% FOR p IN policies %]
var opening_slots = [% To.json(p.opening_slots.unblessed) | $raw %];
var slots_per_day = {};
opening_slots.forEach(function(slot){
let day = slot.day;
if(!slots_per_day[day]) slots_per_day[day] = [];
slots_per_day[day].push(slot);
});
policies['[% p.branchcode | html %]'].slots_per_day = slots_per_day;
[% IF p.enable_waiting_holds_only %]
[% SET waiting_holds = logged_in_user.holds.search( found => 'W', branchcode => p.branchcode ) %]
[% UNLESS waiting_holds.count %]
policies['[% p.branchcode | html %]'].enabled = 0;
[% END %]
[% END %]
[% END %]
let existingPickupMoments = [];
pickups.forEach(function(pickup){
let scheduled_pickup_datetime = pickup.scheduled_pickup_datetime;
let pickupMoment = dayjs(scheduled_pickup_datetime);
if(!existingPickupMoments[pickup.branchcode]) existingPickupMoments[pickup.branchcode] = [];
existingPickupMoments[pickup.branchcode].push(pickupMoment);
});
$(document).ready(function() {
$('#pickups-table').dataTable($.extend(true, {}, dataTablesDefaults, {
"searching": false,
"paging": false,
"info": false,
"autoWidth": false,
"responsive": {
"details": { "type": 'column',"target": -1 }
},
"columnDefs": [
{ "className": 'dtr-control', "orderable": false, "targets": -1 }
],
}));
$("#pickup-branch option").each(function(){
if ( $(this).val() != "" && !policies[$(this).val()].enabled ) {
$(this).prop("disabled", "disabled");
$(this).attr("title", _("You don't have waiting holds at this library"));
}
});
const pickupDate_fp = document.getElementById("pickup-date")._flatpickr;
$('#pickup-branch').on('change', function() {
let branchcode = $(this).val();
let existing_pickup = pickup_exists_in.indexOf(branchcode) != -1;
$('#pickup-date').val("");
$('#pickup-time').val("");
$('#pickup-times').hide();
$('#schedule-pickup-button').prop('disabled', true);
if (existing_pickup) {
$('#existing-pickup-warning').show();
$("#pickup-date,#pickup_date_item,#pickup_notes_item").hide();
} else {
$('#existing-pickup-warning').hide();
$("#pickup-date,#pickup_date_item").show();
}
pickupDate_fp.set('disable', [function(date) {
return !policies[branchcode].slots_per_day.hasOwnProperty(date.getDay());
}]);
});
pickupDate_fp.config.onClose.push(function( selectedDates, dateStr, instance ){
/* Here we add an onClose event to the existing flatpickr instance */
/* It fires after the user has selected a date from the calendar popup */
$('#pickup-times').html("<label>" + _("Select a time") + ":</label><div id=\"pickup-time-slots\"></div>");
$('#schedule-pickup-button').prop( 'disabled', 1 );
var currentDate = dateStr;
let branchcode = $("#pickup-branch").val();
let policy = policies[branchcode];
let selectedDate = dayjs(currentDate);
let pickupSlots = [];
let available_count = 0;
let dow = selectedDate.day(); // Sunday is 0 (at least for now)
if (!policy.slots_per_day[dow]){
$('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>");
return;
}
policy.slots_per_day[dow].sort((a, b) => a.start_hour - b.start_hour).forEach(function(slot){
let pickup_interval = policy.pickup_interval;
if (!pickup_interval) {
$('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>");
return;
}
let listStartMoment = selectedDate.hour(slot.start_hour).minute(slot.start_minute);
let listEndMoment = selectedDate.hour(slot.end_hour).minute(slot.end_minute);
let keep_going = true;
let now = dayjs();
// Initialize pickup slots starting at opening time
let pickupIntervalStartMoment = listStartMoment;
let pickupIntervalEndMoment = listStartMoment.add(pickup_interval, 'minutes');
while (keep_going) {
let available = true;
let display_slot = true;
if (pickupIntervalStartMoment.isBefore(now)) {
// Slots in the past are unavailable
available = false;
display_slot = false;
}
if (pickupIntervalEndMoment.isAfter(listEndMoment)) {
// Slots after the end of pickup times for the day are unavailable
available = false;
}
let pickups_scheduled = 0;
if (existingPickupMoments[branchcode]){
existingPickupMoments[branchcode].forEach(function(pickupMoment){
// An existing pickup time
if (pickupMoment.isSameOrAfter(pickupIntervalStartMoment) && pickupMoment.isBefore(pickupIntervalEndMoment)) {
// This calculated pickup is in use by another scheduled pickup
pickups_scheduled++;
}
});
}
if (pickups_scheduled >= policy.patrons_per_interval) {
available = false;
}
if ( display_slot ) {
pickupSlots.push(
{
"available": available,
"moment": pickupIntervalStartMoment,
"pickups_scheduled": pickups_scheduled
}
);
}
if ( available ) {
available_count++;
}
pickupIntervalStartMoment = pickupIntervalEndMoment;
pickupIntervalEndMoment = pickupIntervalStartMoment.add(pickup_interval, 'minutes');
if (pickupIntervalEndMoment.isAfter(listEndMoment)) {
// This latest slot is after the end of pickup times for the day, so we can stop
keep_going = false;
}
}
$('#schedule-pickup-button').prop( 'disabled', available_count <= 0 );
});
for (let i = 0; i < pickupSlots.length; i++) {
let pickupSlot = pickupSlots[i];
let optText = pickupSlot.moment.format("HH:mm");
let optValue = pickupSlot.moment.format("YYYY-MM-DD HH:mm:ss");
let pickups_scheduled = pickupSlot.pickups_scheduled;
let pickups_available = policy.patrons_per_interval - pickups_scheduled;
let disabled = pickupSlot.available ? "" : "disabled";
$("#pickup-time-slots").append(`<span class="pickup_time"><input type="radio" id="slot_${i}" name="pickup_time" value="${optValue}" ${disabled} /> <label class="pickup_select" for="slot_${i}" data-toggle="tooltip" title="` + _("Appointments available: ") + `${pickups_available}">${optText} <span class="pickups_available">${pickups_available}</span></label></span>`);
}
$("#pickup_notes_item,#pickup-times").show();
});
$("#pickup_date_item,#pickup_notes_item,#pickup-times").hide();
$("#create-pickup").on('submit', function(){
if ( ! $("input[type='radio']:checked").length ) {
alert(_("Please select a date and a pickup time"));
return false;
}
return true;
});
$("#pickup-times").tooltip({
selector: ".pickup_select"
});
});
</script>
[% END %]