Bug 30650: Don't display slots that have are before now
[koha.git] / koha-tmpl / opac-tmpl / bootstrap / en / modules / opac-curbside-pickups.tt
1 [% USE raw %]
2 [% USE To %]
3 [% USE Koha %]
4 [% USE KohaDates %]
5 [% USE Branches %]
6 [% USE AdditionalContents %]
7 [% SET OpacNav = AdditionalContents.get( location => "OpacNav", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %]
8 [% SET OpacNavBottom = AdditionalContents.get( location => "OpacNavBottom", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %]
9 [% INCLUDE 'doc-head-open.inc' %]
10 <title>Your curbside pickups &rsaquo; [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
11
12 <style>
13     .pickup_time input[type='radio'] {
14         display: none;
15     }
16     .pickup_time {
17         /*margin: .2em;*/
18     }
19     .pickup_time label {
20         background-color: #ffffcc;
21         display: inline-block;
22         cursor: pointer;
23     }
24     .pickup_time input[type='radio']:checked + label {
25         background-color: #bcdb89;
26     }
27 </style>
28
29 [% INCLUDE 'doc-head-close.inc' %]
30 [% BLOCK cssinclude %][% END %]
31 </head>
32 [% INCLUDE 'bodytag.inc' bodyid='opac-curside-pickups' bodyclass='scrollto' %]
33 [% INCLUDE 'masthead.inc' %]
34 <div class="main">
35     <nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumbs">
36         <ol class="breadcrumb">
37             <li class="breadcrumb-item">
38                 <a href="/cgi-bin/koha/opac-main.pl">Home</a>
39             </li>
40             <li class="breadcrumb-item">
41                 <a href="/cgi-bin/koha/opac-user.pl">[% INCLUDE 'patron-title.inc' patron = logged_in_user %]</a>
42             </li>
43
44             <li class="breadcrumb-item active">
45                 <a href="#" aria-current="page">Curbside pickups</a>
46             </li>
47         </ol> <!-- / .breadcrumb -->
48     </nav> <!-- /#breadcrumbs -->
49
50     <div class="container-fluid">
51         <div class="row">
52             <div class="col col-lg-2 order-2 order-lg-1">
53                 <div id="navigation">
54                     [% INCLUDE 'navigation.inc' IsPatronPage=1 %]
55                 </div>
56             </div>
57             <div class="col-md-12 col-lg-10 order-1">
58                 <div id='pickupdetails' class="maincontent">
59                     <h2>Curbside pickups</h2>
60
61                     [% FOR m IN messages %]
62                         [% IF m.type == "error" %]
63                         <div class="alert alert-warning">
64                         [% ELSE %]
65                         <div class="alert alert-info">
66                         [% END %]
67                             [% SWITCH m.code %]
68                             [% CASE 'not_enabled' %]
69                                 <span>The curbside pickup feature is not enabled for this library.</span>
70                             [% CASE 'library_is_closed' %]
71                                 <span>Cannot create a curbside pickup for this day, it is an holiday.</span>
72                             [% CASE 'no_waiting_holds' %]
73                                 <span>This patron does not have waiting holds.</span>
74                             [% CASE 'too_many_pickups' %]
75                                 <span>You already have a scheduled pickup for this library.</span>
76                             [% CASE 'no_matching_slots' %]
77                                 <span>Wrong slot selected.</span>
78                             [% CASE 'no_more_pickups_available' %]
79                                 <span>There are no more pickups available for this slot. Please choose another one.</span>
80                             [% CASE 'cannot_checkout' %]
81                                 <span>Unable to check the items out to [% INCLUDE 'patron-title.inc' patron=m.patron %]</span>
82                             [% CASE 'library_notified' %]
83                                 <span>The library has been notified of your arrival.</span>
84                             [% CASE %]
85                                 <span>[% m.code | html %]</span>
86                             [% END %]
87                         </div>
88                     [% END %]
89
90                     <div id="opac-pickups-views" class="toptabs">
91                         <ul class="nav nav-tabs" role="tablist">
92                             <li class="nav-item" role="presentation" id="tab-user-pickups">
93                             [% IF patron_curbside_pickups.count %]
94                                 <a href="#user-pickups" class="nav-link active" aria-controls="user-pickups" aria-selected="true" role="tab" data-toggle="tab">Your pickups</a>
95                             [% ELSE %]
96                                 <a href="#user-pickups" class="nav-link" aria-controls="user-pickups" role="tab" data-toggle="tab">Your pickups</a>
97                             [% END %]
98                             </li>
99
100                             [% IF policies.count %]
101                                 <li class="nav-item" role="presentation" id="tab-user-schedule-pickup">
102                                 [% IF patron_curbside_pickups.count %]
103                                     <a href="#user-schedule-pickup" class="nav-link" aria-controls="user-schedule-pickup" role="tab" data-toggle="tab">Schedule a pickup</a>
104                                 [% ELSE %]
105                                     <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>
106                                 [% END %]
107                                 </li>
108                             [% END %]
109                         </ul>
110
111                         <div class="tab-content">
112                             [% IF patron_curbside_pickups.count %]
113                             <div role="tabpanel" class="tab-pane active" id="user-pickups" aria-labelledby="tab-user-pickups">
114                             [% ELSE %]
115                             <div role="tabpanel" class="tab-pane" id="user-pickups" aria-labelledby="tab-user-pickups">
116                             [% END %]
117                                 [% IF patron_curbside_pickups.count %]
118                                     <table id="pickups-table" class="table table-striped">
119                                         <thead>
120                                             <tr>
121                                                 <th>Pickup library</td>
122                                                 <th>Schedule</th>
123                                                 <th>Notes</th>
124                                                 <th>Actions</th>
125                                             </tr>
126                                         </thead>
127                                         <tbody>
128                                             [% FOR p IN patron_curbside_pickups %]
129                                                 <tr>
130                                                     <td>[% Branches.GetName(p.branchcode) | html %]</td>
131                                                     <td>[% p.scheduled_pickup_datetime | $KohaDates with_hours => 1 %]</td>
132                                                     <td>[% p.notes | html %]</td>
133                                                     <td>
134                                                         <form method="post">
135                                                             <input type="hidden" name="op" value="arrival-alert" />
136                                                             <input type="hidden" name="pickup_id" value="[% p.id | html %]" />
137                                                             [% IF ! p.staged_datetime || p.arrival_datetime %]
138                                                             <button class="btn disabled" disabled href="#" >
139                                                             [% ELSE %]
140                                                             <button type="submit" class="btn" href="#" >
141                                                             [% END %]
142                                                             <i class="fa fa-bell" aria-hidden="true"></i> Alert staff of your arrival
143                                                             </button>
144                                                         </form>
145                                                         <p>
146                                                         <form method="post">
147                                                             <input type="hidden" name="op" value="cancel-pickup" />
148                                                             <input type="hidden" name="pickup_id" value="[% p.id | html %]" />
149                                                             [% IF p.delivered_datetime %]
150                                                                 <button class="btn disabled" disabled href="#" >
151                                                             [% ELSE %]
152                                                                 <button type="submit" class="btn" href="#" >
153                                                             [% END %]
154                                                                 <i class="fa fa-ban" aria-hidden="true"></i> Cancel this pickup</button>
155                                                         </form>
156                                                     </td>
157                                                 </tr>
158                                             [% END %]
159                                         </tbody>
160                                     </table>
161                                 [% ELSE %]
162                                     <div>No curbside pickups.</div>
163                                 [% END %]
164                             </div>
165
166                             [% IF policies.count %]
167                                 [% IF patron_curbside_pickups.count %]
168                                 <div role="tabpanel" class="tab-pane" id="user-schedule-pickup" aria-labelledby="tab-user-schedule-pickup">
169                                 [% ELSE %]
170                                 <div role="tabpanel" class="tab-pane active" id="user-schedule-pickup" aria-labelledby="tab-user-schedule-pickup">
171                                 [% END %]
172                                     <form id="create-pickup" method="post">
173                                         <fieldset class="rows">
174                                             <ol>
175                                                 <li>
176                                                     <label for="pickup_branch">Pickup library:</label>
177                                                     <select name="pickup_branch" id="pickup-branch">
178                                                         <option value="">Select a library</option>
179                                                         [% FOR p IN policies %]
180                                                             <option value="[% p.branchcode | html %]">[% Branches.GetName(p.branchcode) | html %]</option>
181
182                                                         [% END %]
183                                                     </select>
184                                                     <span id="existing-pickup-warning" class="required" style="display: none;">You already have a pickup scheduled for this library.</span>
185                                                 </li>
186                                                 <li>
187                                                     <label for="pickup_date">Pickup date:</label>
188                                                     <input name="pickup_date" type="text" class="flatpickr" id="pickup-date" disabled required="required"/>
189                                                     <span class="required">Required</span>
190                                                 </li>
191
192                                                 <li id="pickup-times" class="radio"></li>
193
194                                                 <li>
195                                                     <label for="notes">Notes:</label>
196                                                     <input name="notes" id="notes" />
197                                                 </li>
198                                             </ol>
199                                         </fieldset>
200
201                                         <fieldset class="action">
202                                             <input type="hidden" name="op" value="create-pickup" />
203                                             <input type="submit" id="schedule-pickup-button" class="btn btn-default" disabled value="Schedule pickup" />
204                                         </fieldset>
205                                     </form>
206                                 </div>
207                             [% END %]
208                         </div>
209                     </div>
210                 </div>
211             </div> <!-- / .col-lg-10 -->
212         </div> <!-- / .row -->
213     </div> <!-- / .container-fluid -->
214 </div> <!-- / .main -->
215
216 [% INCLUDE 'opac-bottom.inc' %]
217
218 [% BLOCK jsinclude %]
219     [% Asset.js("lib/dayjs/dayjs.min.js") | $raw %]
220     [% Asset.js("lib/dayjs/plugin/isSameOrAfter.js") | $raw %]
221     <script>dayjs.extend(window.dayjs_plugin_isSameOrAfter)</script>
222     [% INCLUDE 'calendar.inc' %]
223     <script>
224         [% SET pickup_exists_in = [] %]
225         [% FOR p IN patron_curbside_pickups %]
226             [% UNLESS p.delivered_by  %]
227                 [% pickup_exists_in.push(p.branchcode) %]
228             [% END %]
229         [% END %]
230         let pickup_exists_in = [% To.json(pickup_exists_in.unique()) | $raw %];
231
232         let pickups = [% To.json(curbside_pickups.unblessed) | $raw %];
233         let policies = [% To.json(policies.unblessed) | $raw %];
234         policies = policies.reduce((map, e) => {
235             map[e.branchcode] = e;
236             return map;
237         }, {});
238         let can_schedule_at = {};
239         [% FOR p IN policies %]
240             var opening_slots = [% To.json(p.opening_slots.unblessed) | $raw %];
241             var slots_per_day = {};
242             opening_slots.forEach(function(slot){
243                 let day = slot.day;
244                 if(!slots_per_day[day]) slots_per_day[day] = [];
245                 slots_per_day[day].push(slot);
246             });
247             policies['[% p.branchcode | html %]'].slots_per_day = slots_per_day;
248
249             [% IF p.enable_waiting_holds_only %]
250                 [% SET waiting_holds = logged_in_user.holds.search( found => 'W', branchcode => p.branchcode ) %]
251                 [% UNLESS waiting_holds.count %]
252                     policies['[% p.branchcode | html %]'].enabled = 0;
253                 [% END %]
254             [% END %]
255         [% END %]
256
257         let existingPickupMoments = [];
258         pickups.forEach(function(pickup){
259             let scheduled_pickup_datetime = pickup.scheduled_pickup_datetime;
260             let pickupMoment = dayjs(scheduled_pickup_datetime);
261
262             if(!existingPickupMoments[pickup.branchcode]) existingPickupMoments[pickup.branchcode] = [];
263             existingPickupMoments[pickup.branchcode].push(pickupMoment);
264         });
265
266         $("#pickup-branch option").each(function(){
267             if ( $(this).val() != "" && !policies[$(this).val()].enabled ) {
268                 $(this).prop("disabled", "disabled");
269                 $(this).attr("title", _("You don't have waiting holds at this location"));
270             }
271         });
272
273         $('#pickup-branch').on('change', function() {
274             let branchcode = $(this).val();
275
276             let existing_pickup = pickup_exists_in.indexOf(branchcode) != -1;
277
278             $('#pickup-date').val("");
279             $('#pickup-time').val("");
280             $('#pickup-times').hide();
281             $('#schedule-pickup-button').prop('disabled', true);
282
283             if (existing_pickup) {
284                 $('#existing-pickup-warning').show();
285                 $('#pickup-date').prop("disabled", true);
286             } else {
287                 $('#existing-pickup-warning').hide();
288                 $('#pickup-date').prop("disabled", branchcode == "");
289             }
290         });
291
292         $("#pickup-date").on('change', function() {
293
294             $('#pickup-times').empty();
295             $('#schedule-pickup-button').prop( 'disabled', 1 );
296
297             var currentDate = $(this).val();
298             let branchcode = $("#pickup-branch").val();
299             let policy = policies[branchcode];
300
301             let selectedDate = dayjs(currentDate);
302
303             let pickupSlots = [];
304             let available_count = 0;
305             let dow = selectedDate.day(); // Sunday is 0 (at least for now)
306             if (!policy.slots_per_day[dow]){
307                 $('#pickup-times').html("<div>"+_("No pickup time define for this day.")+"</div>");
308                 return;
309             }
310
311             policy.slots_per_day[dow].forEach(function(slot){
312                 let pickup_interval = policy.pickup_interval;
313                 if (!pickup_interval) {
314                     $('#pickup-times').html("<div>"+_("No pickup time define for this day.")+"</div>");
315                     return;
316                 }
317
318                 let listStartMoment = selectedDate.hour(slot.start_hour).minute(slot.start_minute);
319                 let listEndMoment = selectedDate.hour(slot.end_hour).minute(slot.end_minute);
320
321                 let keep_going = true;
322                 let now = dayjs();
323
324                 // Initialize pickup slots starting at opening time
325                 let pickupIntervalStartMoment = listStartMoment;
326                 let pickupIntervalEndMoment   = listStartMoment.add(pickup_interval, 'minutes');
327                 while (keep_going) {
328                     let available = true;
329                     let display_slot = true;
330
331                     if (pickupIntervalStartMoment.isBefore(now)) {
332                         // Slots in the past are unavailable
333                         available = false;
334                         display_slot = false;
335                     }
336
337                     if (pickupIntervalEndMoment.isAfter(listEndMoment)) {
338                         // Slots after the end of pickup times for the day are unavailable
339                         available = false;
340                     }
341
342                     let pickups_scheduled = 0;
343
344                     if (existingPickupMoments[branchcode]){
345                         existingPickupMoments[branchcode].forEach(function(pickupMoment){
346                             // An existing pickup time
347                             if (pickupMoment.isSameOrAfter(pickupIntervalStartMoment) && pickupMoment.isBefore(pickupIntervalEndMoment)) {
348                                 // This calculated pickup is in use by another scheduled pickup
349                                 pickups_scheduled++;
350                             }
351                         });
352                     }
353
354                     if (pickups_scheduled >= policy.patrons_per_interval) {
355                         available = false;
356                     }
357
358                     if ( display_slot ) {
359                         pickupSlots.push(
360                             {
361                                 "available": available,
362                                 "moment": pickupIntervalStartMoment,
363                                 "pickups_scheduled": pickups_scheduled
364                             }
365                         );
366                     }
367
368                     if ( available ) {
369                         available_count++;
370                     }
371
372                     pickupIntervalStartMoment = pickupIntervalEndMoment;
373                     pickupIntervalEndMoment = pickupIntervalStartMoment.add(pickup_interval, 'minutes');
374                     if (pickupIntervalEndMoment.isAfter(listEndMoment)) {
375                         // This latest slot is after the end of pickup times for the day, so we can stop
376                         keep_going = false;
377                     }
378                 }
379
380                 $('#schedule-pickup-button').prop( 'disabled', available_count <= 0 );
381             });
382
383             for (let i = 0; i < pickupSlots.length; i++) {
384                 let pickupSlot = pickupSlots[i];
385                 let optText = pickupSlot.moment.format("HH:mm");
386                 let optValue = pickupSlot.moment.format("YYYY-MM-DD HH:mm:ss");
387                 let pickups_scheduled = pickupSlot.pickups_scheduled;
388                 let disabled = pickupSlot.available ? "" : "disabled";
389                 $("#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>`);
390             }
391
392             $('#pickup-times').show();
393         });
394
395
396         $(document).ready(function() {
397             $("#pickup-times").hide();
398
399             $("#create-pickup").on('submit', function(){
400                 if ( ! $("input[type='radio']:checked").length ) {
401                     alert(_("Please select a date and a pickup time"))
402                     return false;
403                 }
404                 return true;
405             });
406         });
407     </script>
408 [% END %]