View | Details | Raw Unified | Return to bug 31265
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/curbside_pickups.tt (+5 lines)
Lines 10-15 Link Here
10
[% INCLUDE 'doc-head-open.inc' %]
10
[% INCLUDE 'doc-head-open.inc' %]
11
<title>Curbside pickups &rsaquo; Circulation &rsaquo; Koha</title>
11
<title>Curbside pickups &rsaquo; Circulation &rsaquo; Koha</title>
12
<style>
12
<style>
13
    #pickup-times {
14
        width: 50%;
15
    }
13
    .pickup_time input[type='radio'] {
16
    .pickup_time input[type='radio'] {
14
        display: none;
17
        display: none;
15
    }
18
    }
Lines 20-25 Link Here
20
        background-color: #ffffcc;
23
        background-color: #ffffcc;
21
        display: inline-block;
24
        display: inline-block;
22
        cursor: pointer;
25
        cursor: pointer;
26
        width: 5rem;
27
        text-align: center;
23
    }
28
    }
24
    .pickup_time input[type='radio']:checked + label {
29
    .pickup_time input[type='radio']:checked + label {
25
        background-color: #bcdb89;
30
        background-color: #bcdb89;
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-curbside-pickups.tt (-139 / +204 lines)
Lines 8-31 Link Here
8
[% SET OpacNavBottom = AdditionalContents.get( location => "OpacNavBottom", 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' %]
9
[% INCLUDE 'doc-head-open.inc' %]
10
<title>Your curbside pickups &rsaquo; [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
10
<title>Your curbside pickups &rsaquo; [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
11
11
[% FILTER collapse %]
12
<style>
12
    <style>
13
    .pickup_time input[type='radio'] {
13
        .pickup_time input[type='radio'] {
14
        display: none;
14
            display: none;
15
    }
15
        }
16
    .pickup_time {
16
        .pickup_time {
17
        /*margin: .2em;*/
17
            display: inline-block;
18
    }
18
        }
19
    .pickup_time label {
19
        #pickup-times {
20
        background-color: #ffffcc;
20
            display: flex;
21
        display: inline-block;
21
            flex-wrap: wrap;
22
        cursor: pointer;
22
        }
23
    }
23
        #pickup-times label {
24
    .pickup_time input[type='radio']:checked + label {
24
            flex-basis: 100%;
25
        background-color: #bcdb89;
25
            text-align: center;
26
    }
26
        }
27
</style>
27
        fieldset.rows .pickup_time label {
28
28
            background: #e6e6e6 linear-gradient(180deg,#f0f0f0,#e6e6e6);
29
            border: 1px solid #b3b3b3;
30
            border-radius: 3px;
31
            cursor: pointer;
32
            float: none;
33
            font-weight: normal;
34
            padding: .2em .1em;
35
            text-align: center;
36
            width: 6rem;
37
        }
38
        fieldset.rows .pickup_time label:hover {
39
            background: #e6e6e6 linear-gradient(180deg,#F7F7F7,#F0F0F0);
40
        }
41
        fieldset.rows .pickup_time label::before {
42
            font-family: FontAwesome;
43
            color: #AAA;
44
            content: "\f1db";
45
            display: inline-block;
46
            padding-right: .5em;
47
        }
48
        fieldset.rows .pickup_time input:checked + label {
49
            background: #5cb85c linear-gradient(180deg,#5cb85c,#4cae4c);
50
            border: 1px solid #548e54;
51
            color: #FFF;
52
        }
53
        fieldset.rows .pickup_time input:checked + label::before {
54
            font-family: FontAwesome;
55
            color: #FFF;
56
            content: "\f05d";
57
            display: inline-block;
58
            padding-right: .5em;
59
        }
60
        fieldset.rows .pickup_time input:disabled + label {
61
            background: #F0F0F0 none;
62
            border: 1px solid #E6E6E6;
63
            color: #6c6c6c;
64
        }
65
        fieldset.rows .pickup_time input:disabled + label:hover {
66
            cursor: not-allowed;
67
        }
68
        fieldset.rows .pickup_time input:disabled + label::before {
69
            content: "";
70
            display: inline-block;
71
        }
72
        #existing-pickup-warning {
73
            color: #c00;
74
            display: inline-block;
75
        }
76
        .pickups_available {
77
            font-size: 90%;
78
        }
79
        .pickups_available::before {
80
            content: "(";
81
        }
82
        .pickups_available::after {
83
            content: ")";
84
        }
85
    </style>
86
[% END %]
29
[% INCLUDE 'doc-head-close.inc' %]
87
[% INCLUDE 'doc-head-close.inc' %]
30
[% BLOCK cssinclude %][% END %]
88
[% BLOCK cssinclude %][% END %]
31
</head>
89
</head>
Lines 173-180 Link Here
173
                                        <fieldset class="rows">
231
                                        <fieldset class="rows">
174
                                            <ol>
232
                                            <ol>
175
                                                <li>
233
                                                <li>
176
                                                    <label for="pickup_branch">Pickup library:</label>
234
                                                    <label for="pickup-branch" class="required">Pickup library:</label>
177
                                                    <select name="pickup_branch" id="pickup-branch">
235
                                                    <select name="pickup_branch" id="pickup-branch" required="required">
178
                                                        <option value="">Select a library</option>
236
                                                        <option value="">Select a library</option>
179
                                                        [% FOR p IN policies %]
237
                                                        [% FOR p IN policies %]
180
                                                            <option value="[% p.branchcode | html %]">[% Branches.GetName(p.branchcode) | html %]</option>
238
                                                            <option value="[% p.branchcode | html %]">[% Branches.GetName(p.branchcode) | html %]</option>
Lines 182-197 Link Here
182
                                                        [% END %]
240
                                                        [% END %]
183
                                                    </select>
241
                                                    </select>
184
                                                    <span id="existing-pickup-warning" class="required" style="display: none;">You already have a pickup scheduled for this library.</span>
242
                                                    <span id="existing-pickup-warning" class="required" style="display: none;">You already have a pickup scheduled for this library.</span>
243
                                                    <div class="required_label required">Required</div>
185
                                                </li>
244
                                                </li>
186
                                                <li>
245
                                                <li id="pickup_date_item">
187
                                                    <label for="pickup_date">Pickup date:</label>
246
                                                    <label for="pickup-date">Pickup date:</label>
188
                                                    <input name="pickup_date" type="text" class="flatpickr" id="pickup-date" disabled required="required"/>
247
                                                    <input name="pickup_date" type="text" class="flatpickr" id="pickup-date" disabled required="required"/>
189
                                                    <span class="required">Required</span>
248
                                                    <div class="required_label required">Required</div>
190
                                                </li>
249
                                                </li>
191
250
192
                                                <li id="pickup-times" class="radio"></li>
251
                                                <li id="pickup-times"></li>
193
252
194
                                                <li>
253
                                                <li id="pickup_notes_item">
195
                                                    <label for="notes">Notes:</label>
254
                                                    <label for="notes">Notes:</label>
196
                                                    <input name="notes" id="notes" />
255
                                                    <input name="notes" id="notes" />
197
                                                </li>
256
                                                </li>
Lines 265-410 Link Here
265
            existingPickupMoments[pickup.branchcode].push(pickupMoment);
324
            existingPickupMoments[pickup.branchcode].push(pickupMoment);
266
        });
325
        });
267
326
268
        $("#pickup-branch option").each(function(){
327
        $(document).ready(function() {
269
            if ( $(this).val() != "" && !policies[$(this).val()].enabled ) {
328
            $("#pickup-branch option").each(function(){
270
                $(this).prop("disabled", "disabled");
329
                if ( $(this).val() != "" && !policies[$(this).val()].enabled ) {
271
                $(this).attr("title", _("You don't have waiting holds at this library"));
330
                    $(this).prop("disabled", "disabled");
272
            }
331
                    $(this).attr("title", _("You don't have waiting holds at this library"));
273
        });
332
                }
274
333
            });
275
        $('#pickup-branch').on('change', function() {
276
            let branchcode = $(this).val();
277
278
            let existing_pickup = pickup_exists_in.indexOf(branchcode) != -1;
279
334
280
            $('#pickup-date').val("");
335
            $('#pickup-branch').on('change', function() {
281
            $('#pickup-time').val("");
336
                let branchcode = $(this).val();
282
            $('#pickup-times').hide();
283
            $('#schedule-pickup-button').prop('disabled', true);
284
337
285
            if (existing_pickup) {
338
                let existing_pickup = pickup_exists_in.indexOf(branchcode) != -1;
286
                $('#existing-pickup-warning').show();
287
                $('#pickup-date').prop("disabled", true);
288
            } else {
289
                $('#existing-pickup-warning').hide();
290
                $('#pickup-date').prop("disabled", branchcode == "");
291
            }
292
        });
293
339
294
        $("#pickup-date").on('change', function() {
340
                $('#pickup-date').val("");
341
                $('#pickup-time').val("");
342
                $('#pickup-times').hide();
343
                $('#schedule-pickup-button').prop('disabled', true);
295
344
296
            $('#pickup-times').empty();
345
                if (existing_pickup) {
297
            $('#schedule-pickup-button').prop( 'disabled', 1 );
346
                    $('#existing-pickup-warning').show();
347
                    $("#pickup_date_item,#pickup_notes_item").hide();
348
                    $('#pickup-date').prop("disabled", true);
349
                } else {
350
                    $('#existing-pickup-warning').hide();
351
                    $("#pickup_date_item").show();
352
                    $('#pickup-date').prop("disabled", branchcode == "");
353
                }
354
            });
298
355
299
            var currentDate = $(this).val();
356
            const pickupDate = document.getElementById("pickup-date");
300
            let branchcode = $("#pickup-branch").val();
357
            pickupDate._flatpickr.config.onClose.push(function( selectedDates, dateStr, instance ){
301
            let policy = policies[branchcode];
358
                /* Here we add an onClose event to the existing flatpickr instance */
359
                /* It fires after the user has selected a date from the calendar popup */
360
                $('#pickup-times').html("<label>" + _("Select a time") + "</label>");
361
                $('#schedule-pickup-button').prop( 'disabled', 1 );
302
362
303
            let selectedDate = dayjs(currentDate, get_dateformat_str(dateformat_pref).toUpperCase());
363
                var currentDate = dateStr;
364
                let branchcode = $("#pickup-branch").val();
365
                let policy = policies[branchcode];
304
366
305
            let pickupSlots = [];
367
                let selectedDate = dayjs(currentDate, get_dateformat_str(dateformat_pref).toUpperCase());
306
            let available_count = 0;
307
            let dow = selectedDate.day(); // Sunday is 0 (at least for now)
308
            if (!policy.slots_per_day[dow]){
309
                $('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>");
310
                return;
311
            }
312
368
313
            policy.slots_per_day[dow].forEach(function(slot){
369
                let pickupSlots = [];
314
                let pickup_interval = policy.pickup_interval;
370
                let available_count = 0;
315
                if (!pickup_interval) {
371
                let dow = selectedDate.day(); // Sunday is 0 (at least for now)
372
                if (!policy.slots_per_day[dow]){
316
                    $('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>");
373
                    $('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>");
317
                    return;
374
                    return;
318
                }
375
                }
319
376
320
                let listStartMoment = selectedDate.hour(slot.start_hour).minute(slot.start_minute);
377
                policy.slots_per_day[dow].forEach(function(slot){
321
                let listEndMoment = selectedDate.hour(slot.end_hour).minute(slot.end_minute);
378
                    let pickup_interval = policy.pickup_interval;
322
379
                    if (!pickup_interval) {
323
                let keep_going = true;
380
                        $('#pickup-times').html("<div>"+_("No pickup time defined for this day.")+"</div>");
324
                let now = dayjs();
381
                        return;
325
326
                // Initialize pickup slots starting at opening time
327
                let pickupIntervalStartMoment = listStartMoment;
328
                let pickupIntervalEndMoment   = listStartMoment.add(pickup_interval, 'minutes');
329
                while (keep_going) {
330
                    let available = true;
331
                    let display_slot = true;
332
333
                    if (pickupIntervalStartMoment.isBefore(now)) {
334
                        // Slots in the past are unavailable
335
                        available = false;
336
                        display_slot = false;
337
                    }
382
                    }
338
383
339
                    if (pickupIntervalEndMoment.isAfter(listEndMoment)) {
384
                    let listStartMoment = selectedDate.hour(slot.start_hour).minute(slot.start_minute);
340
                        // Slots after the end of pickup times for the day are unavailable
385
                    let listEndMoment = selectedDate.hour(slot.end_hour).minute(slot.end_minute);
341
                        available = false;
386
387
                    let keep_going = true;
388
                    let now = dayjs();
389
390
                    // Initialize pickup slots starting at opening time
391
                    let pickupIntervalStartMoment = listStartMoment;
392
                    let pickupIntervalEndMoment   = listStartMoment.add(pickup_interval, 'minutes');
393
                    while (keep_going) {
394
                        let available = true;
395
                        let display_slot = true;
396
397
                        if (pickupIntervalStartMoment.isBefore(now)) {
398
                            // Slots in the past are unavailable
399
                            available = false;
400
                            display_slot = false;
401
                        }
402
403
                        if (pickupIntervalEndMoment.isAfter(listEndMoment)) {
404
                            // Slots after the end of pickup times for the day are unavailable
405
                            available = false;
406
                        }
407
408
                        let pickups_scheduled = 0;
409
410
                        if (existingPickupMoments[branchcode]){
411
                            existingPickupMoments[branchcode].forEach(function(pickupMoment){
412
                                // An existing pickup time
413
                                if (pickupMoment.isSameOrAfter(pickupIntervalStartMoment) && pickupMoment.isBefore(pickupIntervalEndMoment)) {
414
                                    // This calculated pickup is in use by another scheduled pickup
415
                                    pickups_scheduled++;
416
                                }
417
                            });
418
                        }
419
420
                        if (pickups_scheduled >= policy.patrons_per_interval) {
421
                            available = false;
422
                        }
423
424
                        if ( display_slot ) {
425
                            pickupSlots.push(
426
                                {
427
                                    "available": available,
428
                                    "moment": pickupIntervalStartMoment,
429
                                    "pickups_scheduled": pickups_scheduled
430
                                }
431
                            );
432
                        }
433
434
                        if ( available ) {
435
                            available_count++;
436
                        }
437
438
                        pickupIntervalStartMoment = pickupIntervalEndMoment;
439
                        pickupIntervalEndMoment = pickupIntervalStartMoment.add(pickup_interval, 'minutes');
440
                        if (pickupIntervalEndMoment.isAfter(listEndMoment)) {
441
                            // This latest slot is after the end of pickup times for the day, so we can stop
442
                            keep_going = false;
443
                        }
342
                    }
444
                    }
343
445
344
                    let pickups_scheduled = 0;
446
                    $('#schedule-pickup-button').prop( 'disabled', available_count <= 0 );
345
447
                });
346
                    if (existingPickupMoments[branchcode]){
448
347
                        existingPickupMoments[branchcode].forEach(function(pickupMoment){
449
                for (let i = 0; i < pickupSlots.length; i++) {
348
                            // An existing pickup time
450
                    let pickupSlot = pickupSlots[i];
349
                            if (pickupMoment.isSameOrAfter(pickupIntervalStartMoment) && pickupMoment.isBefore(pickupIntervalEndMoment)) {
451
                    let optText = pickupSlot.moment.format("HH:mm");
350
                                // This calculated pickup is in use by another scheduled pickup
452
                    let optValue = pickupSlot.moment.format("YYYY-MM-DD HH:mm:ss");
351
                                pickups_scheduled++;
453
                    let pickups_scheduled = pickupSlot.pickups_scheduled;
352
                            }
454
                    let pickups_available = policy.patrons_per_interval - pickups_scheduled;
353
                        });
455
                    let disabled = pickupSlot.available ? "" : "disabled";
354
                    }
456
                    $("#pickup-times").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>`);
355
356
                    if (pickups_scheduled >= policy.patrons_per_interval) {
357
                        available = false;
358
                    }
359
360
                    if ( display_slot ) {
361
                        pickupSlots.push(
362
                            {
363
                                "available": available,
364
                                "moment": pickupIntervalStartMoment,
365
                                "pickups_scheduled": pickups_scheduled
366
                            }
367
                        );
368
                    }
369
370
                    if ( available ) {
371
                        available_count++;
372
                    }
373
374
                    pickupIntervalStartMoment = pickupIntervalEndMoment;
375
                    pickupIntervalEndMoment = pickupIntervalStartMoment.add(pickup_interval, 'minutes');
376
                    if (pickupIntervalEndMoment.isAfter(listEndMoment)) {
377
                        // This latest slot is after the end of pickup times for the day, so we can stop
378
                        keep_going = false;
379
                    }
380
                }
457
                }
381
458
382
                $('#schedule-pickup-button').prop( 'disabled', available_count <= 0 );
459
                $("#pickup_notes_item,#pickup-times").show();
383
            });
460
            });
384
461
385
            for (let i = 0; i < pickupSlots.length; i++) {
462
            $("#pickup_date_item,#pickup_notes_item,#pickup-times").hide();
386
                let pickupSlot = pickupSlots[i];
387
                let optText = pickupSlot.moment.format("HH:mm");
388
                let optValue = pickupSlot.moment.format("YYYY-MM-DD HH:mm:ss");
389
                let pickups_scheduled = pickupSlot.pickups_scheduled;
390
                let disabled = pickupSlot.available ? "" : "disabled";
391
                $("#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>`);
392
            }
393
394
            $('#pickup-times').show();
395
        });
396
397
398
        $(document).ready(function() {
399
            $("#pickup-times").hide();
400
463
401
            $("#create-pickup").on('submit', function(){
464
            $("#create-pickup").on('submit', function(){
402
                if ( ! $("input[type='radio']:checked").length ) {
465
                if ( ! $("input[type='radio']:checked").length ) {
403
                    alert(_("Please select a date and a pickup time"))
466
                    alert(_("Please select a date and a pickup time"));
404
                    return false;
467
                    return false;
405
                }
468
                }
406
                return true;
469
                return true;
407
            });
470
            });
471
            $("#pickup-times").tooltip({
472
                selector: ".pickup_select"
473
            });
408
        });
474
        });
409
    </script>
475
    </script>
410
[% END %]
476
[% END %]
411
- 

Return to bug 31265