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

(-)a/koha-tmpl/intranet-tmpl/prog/css/src/bookings.scss (+13 lines)
Line 0 Link Here
1
#bookings-timeline {
2
    .vis-item.vis-range {
3
        &.cancelled {
4
            background: repeating-linear-gradient(
5
                135deg,
6
                rgba(211, 211, 211, 0.5) 0,
7
                rgba(211, 211, 211, 0.5) 10px,
8
                transparent 10px,
9
                transparent 20px
10
            );
11
        }
12
    }
13
}
(-)a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss (+14 lines)
Lines 4895-4897 div .suggestion_note { Link Here
4895
        }
4895
        }
4896
    }
4896
    }
4897
}
4897
}
4898
4899
[class*="filters-"] {
4900
    [data-filtered] span:nth-child(2) {
4901
        display: none;
4902
    }
4903
4904
    a:not([data-filtered]) span:nth-child(1) {
4905
        display: none;
4906
    }
4907
4908
    a {
4909
        cursor: pointer;
4910
    }
4911
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/table-filters.inc (+30 lines)
Line 0 Link Here
1
[%# TABLE FILTER CONTROLS USAGE                                                                          %]
2
[%#    [ INCLUDE 'table-filters.inc'                                                                     %]
3
[%#        filters = [                                                                                   %]
4
[%#            { id = 'expired',   label_show = t('Show expired'),   label_hide = t('Hide expired') },   %]
5
[%#            { id = 'cancelled', label_show = t('Show cancelled'), label_hide = t('Hide cancelled') }, %]
6
[%#            { id = 'completed', label_show = t('Show completed'), label_hide = t('Hide completed') }  %]
7
[%#        ]                                                                                             %]
8
[%#        filter_class = 'bookings'  # Optional, defaults to 'filters'                                  %]
9
[%#    ]                                                                                                 %]
10
[%#                                                                                                      %]
11
[%# Parameters:                                                                                          %]
12
[%#   - filters: Array of filter objects with:                                                           %]
13
[%#       - id: Unique identifier for the filter                                                         %]
14
[%#       - label_show: Text when filter shows items                                                     %]
15
[%#       - label_hide: Text when filter hides items                                                     %]
16
[%#       - active: Boolean - filter starts active (optional, defaults to true)                          %]
17
[%#   - filter_class: CSS class prefix (optional, defaults to 'filters')                                 %]
18
19
[% IF filters && filters.size > 0 %]
20
    [% filter_class = filter_class || 'filters' %]
21
22
    <fieldset class="action filters filters-[% filter_class | html %] d-flex gap-2">
23
        [% FOREACH filter IN filters %]
24
            <a id="filter-[% filter.id | html %]" data-filter="[% filter.id | html %]" [% IF !filter.defined('active') || filter.active %]data-filtered[% END %]>
25
                <span><i class="fa fa-bars"></i> [% filter.label_show | html %]</span>
26
                <span><i class="fa fa-filter"></i> [% filter.label_hide | html %]</span>
27
            </a>
28
        [% END %]
29
    </fieldset>
30
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt (-75 / +88 lines)
Lines 12-24 Link Here
12
    [% END %]</title
12
    [% END %]</title
13
>
13
>
14
[% INCLUDE 'doc-head-close.inc' %]
14
[% INCLUDE 'doc-head-close.inc' %]
15
<style>
15
[% Asset.css("css/bookings.css") | $raw %]
16
    #bookings-timeline .vis-item.vis-range {
17
        &.cancelled {
18
            background: rgba(128, 128, 128, 0.3);
19
        }
20
    }
21
</style>
22
</head>
16
</head>
23
17
24
<body id="circ_request" class="catalog">
18
<body id="circ_request" class="catalog">
Lines 47-56 Link Here
47
        <h1>Bookings for [% INCLUDE 'biblio-title-head.inc' %]</h1>
41
        <h1>Bookings for [% INCLUDE 'biblio-title-head.inc' %]</h1>
48
        <div class="page-section" id="bookings-timeline"></div>
42
        <div class="page-section" id="bookings-timeline"></div>
49
        <div class="page-section">
43
        <div class="page-section">
50
            <fieldset class="action filters d-flex gap-2" style="cursor: pointer;">
44
            [%
51
                <a id="expired_filter" data-filter="expired"><i class="fa fa-bars"></i> Show expired</a>
45
                INCLUDE 'table-filters.inc'
52
                <a id="cancelled_filter" data-filter="cancelled"><i class="fa fa-bars"></i> Show cancelled</a>
46
                filters = [
53
            </fieldset>
47
                    { id = 'expired', label_show = t('Include expired'), label_hide = t('Exclude expired') },
48
                    { id = 'cancelled', label_show = t('Include cancelled'), label_hide = t('Exclude cancelled') },
49
                ]
50
                filter_class = 'bookings'
51
            %]
54
52
55
            <table id="bookings_table"></table>
53
            <table id="bookings_table"></table>
56
        </div>
54
        </div>
Lines 124-129 Link Here
124
                            start: dayjs(booking.start_date).toDate(),
122
                            start: dayjs(booking.start_date).toDate(),
125
                            end: dayjs(booking.end_date).toDate(),
123
                            end: dayjs(booking.end_date).toDate(),
126
                            content: !isActive ? `<s>${patronContent}</s>` : patronContent,
124
                            content: !isActive ? `<s>${patronContent}</s>` : patronContent,
125
                            className: booking.status === "cancelled" ? "cancelled" : "",
127
                            [% IF CAN_user_circulate_manage_bookings %]
126
                            [% IF CAN_user_circulate_manage_bookings %]
128
                            editable: booking.status !== "cancelled" ? { remove: true, updateTime: true } : false,
127
                            editable: booking.status !== "cancelled" ? { remove: true, updateTime: true } : false,
129
                            [% ELSE %]
128
                            [% ELSE %]
Lines 134-150 Link Here
134
                        });
133
                        });
135
                    }
134
                    }
136
135
137
                    const cancelledItems = document.querySelectorAll('#bookings-timeline .vis-item.vis-range.cancelled');
138
                    cancelledItems.forEach(item => {
139
                        item.style.background = 'repeating-linear-gradient(' +
140
                            '135deg,' +
141
                            'rgba(211, 211, 211, 0.5) 0,' +
142
                            'rgba(211, 211, 211, 0.5) 10px,' +
143
                            'transparent 10px,' +
144
                            'transparent 20px' +
145
                        ');';
146
                    });
147
148
                    var container = document.getElementById('bookings-timeline');
136
                    var container = document.getElementById('bookings-timeline');
149
                    var options = {
137
                    var options = {
150
                        stack: true,
138
                        stack: true,
Lines 201-207 Link Here
201
                            });
189
                            });
202
                        },
190
                        },
203
                        onRemove: function (item, callback) {
191
                        onRemove: function (item, callback) {
204
                            $('#cancelBookingModal').modal('show', $('<button data-booking="'+item.id+'">'));
192
                            const button = document.createElement('button');
193
                            button.dataset.booking = item.booking || item.id;
194
                            $('#cancelBookingModal').modal('show', button);
205
                            $('#cancelBookingModal').on('hide.bs.modal', function(e) {
195
                            $('#cancelBookingModal').on('hide.bs.modal', function(e) {
206
                                if (cancel_success) {
196
                                if (cancel_success) {
207
                                    cancel_success = 0;
197
                                    cancel_success = 0;
Lines 219-243 Link Here
219
                }
209
                }
220
            );
210
            );
221
211
222
            const filterStates = { expired: true, cancelled: false };
212
            const filters = ["expired", "cancelled"].reduce((acc, filter) => {
213
                acc[filter] = document.getElementById(`filter-${filter}`);
214
                return acc;
215
            }, {});
216
217
            Object.values(filters).forEach(filter => {
218
                filter.addEventListener("click", handleFilter);
219
            });
220
221
            const isFilterActive = (filter) => "filtered" in filter.dataset;
222
            const isShowingItems = (filter) => !isFilterActive(filter);
223
            const additional_filters = {
223
            const additional_filters = {
224
                end_date: () => {
224
                end_date: () => {
225
                    if (filterStates.expired) {
225
                    if (isShowingItems(filters.expired)) {
226
                        let today = new Date();
226
                        let today = new Date();
227
                        return { ">=": today.toISOString() };
227
                        return { ">=": today.toISOString() };
228
                    }
228
                    }
229
                },
229
                },
230
                status: () => {
230
                status: () => {
231
                    const defaults = ["new", "pending", "active"];
231
                    const defaults = ["new", "pending", "active"];
232
                    if (filterStates.cancelled) {
232
                    const filtered = [...defaults];
233
                        const filtered = [...defaults, "cancelled"];
233
                    if (isShowingItems(filters.cancelled)) {
234
                        return { "-in": filtered };
234
                        filtered.push("cancelled");
235
                    }
235
                    }
236
236
                    return { "-in": filtered };
237
                    return { "-in": defaults };
238
                },
237
                },
239
            };
238
            };
240
239
240
            function handleFilter(e) {
241
                const target = e.target;
242
                const anchor = target.closest("a");
243
244
                if (isFilterActive(anchor)) {
245
                    delete anchor.dataset.filtered;
246
                } else {
247
                    anchor.dataset.filtered = "";
248
                }
249
250
                bookings_table.DataTable().ajax.reload(() => {
251
                    const anyFiltersActive = Object.values(filters).some(isShowingItems);
252
                    bookings_table
253
                        .DataTable()
254
                        .column("status:name")
255
                        .visible(anyFiltersActive, false);
256
                });
257
            }
258
241
            var bookings_table_url = "/api/v1/biblios/%s/bookings".format(biblionumber);
259
            var bookings_table_url = "/api/v1/biblios/%s/bookings".format(biblionumber);
242
            bookings_table = $('#bookings_table').kohaTable({
260
            bookings_table = $('#bookings_table').kohaTable({
243
                "ajax": {
261
                "ajax": {
Lines 374-384 Link Here
374
                    "orderable": false,
392
                    "orderable": false,
375
                    "render": function(data, type, row, meta) {
393
                    "render": function(data, type, row, meta) {
376
                        let result = "";
394
                        let result = "";
377
                        let is_cancelled = row.status === "cancelled";
395
                        const is_readonly = row.status === "cancelled";
378
                        [% IF CAN_user_circulate_manage_bookings %]
396
                        [% IF CAN_user_circulate_manage_bookings %]
379
                        if (!is_cancelled) {
397
                        if (!is_readonly) {
380
                            result += '<button type="button" class="btn btn-default btn-xs edit-action" data-bs-toggle="modal" data-bs-target="#placeBookingModal" data-booking="%s" data-biblionumber="%s" data-itemnumber="%s" data-patron="%s" data-pickup_library="%s" data-start_date="%s" data-end_date="%s" data-item_type_id="%s"><i class="fa fa-pencil" aria-hidden="true"></i> %s</button>'.format(row.booking_id, biblionumber, row.item_id,  row.patron_id, row.pickup_library_id, row.start_date, row.end_date, row.item.item_type_id, _("Edit"));
398
                            result += `
381
                            result += '<button type="button" class="btn btn-default btn-xs cancel-action" data-bs-toggle="modal" data-bs-target="#cancelBookingModal" data-booking="%s"><i class="fa fa-trash" aria-hidden="true"></i> %s</button>'.format(row.booking_id, _("Cancel"));
399
                                <button type="button" class="btn btn-default btn-xs edit-action"
400
                                    data-bs-toggle="modal"
401
                                    data-bs-target="#placeBookingModal"
402
                                    data-booking="%s"
403
                                    data-biblionumber="%s"
404
                                    data-itemnumber="%s"
405
                                    data-patron="%s"
406
                                    data-pickup_library="%s"
407
                                    data-start_date="%s"
408
                                    data-end_date="%s"
409
                                    data-item_type_id="%s"
410
                                >
411
                                    <i class="fa fa-pencil" aria-hidden="true"></i> %s
412
                                </button>
413
                            `.format(
414
                                escape_str(row.booking_id),
415
                                escape_str(biblionumber),
416
                                escape_str(row.item_id),
417
                                escape_str(row.patron_id),
418
                                escape_str(row.pickup_library_id),
419
                                escape_str(row.start_date),
420
                                escape_str(row.end_date),
421
                                escape_str(row.item.item_type_id),
422
                                _("Edit")
423
                            );
424
                            result += `
425
                                <button type="button" class="btn btn-default btn-xs cancel-action"
426
                                    data-bs-toggle="modal"
427
                                    data-bs-target="#cancelBookingModal"
428
                                    data-booking="%s"
429
                                >
430
                                    <i class="fa fa-trash" aria-hidden="true"></i> %s
431
                                </button>
432
                            `.format(
433
                                escape_str(row.booking_id),
434
                                _("Cancel")
435
                            );
382
                        }
436
                        }
383
                        [% END %]
437
                        [% END %]
384
                        return result;
438
                        return result;
Lines 386-436 Link Here
386
                }]
440
                }]
387
            }, undefined, 0, additional_filters);
441
            }, undefined, 0, additional_filters);
388
442
389
            document
390
                .getElementById("expired_filter")
391
                .addEventListener("click", (e) =>
392
                    handleFilter(
393
                        e,
394
                        { active: _("Hide expired"), inactive: _("Show expired") },
395
                        filterStates,
396
                    ),
397
                );
398
399
            document
400
                .getElementById("cancelled_filter")
401
                .addEventListener("click", (e) =>
402
                    handleFilter(
403
                        e,
404
                        { active: _("Hide cancelled"), inactive: _("Show cancelled") },
405
                        filterStates,
406
                    ),
407
                );
408
409
            $("#cancellation-reason").comboBox({
443
            $("#cancellation-reason").comboBox({
410
                displayProperty: 'name',
444
                displayProperty: 'name',
411
                placeholder: _("Select or type a reason"),
445
                placeholder: _("Select or type a reason"),
412
            });
446
            });
413
447
414
            function handleFilter(e, text, filterStates) {
415
                const target = e.target;
416
                const { filter } = target.dataset;
417
                if (target.classList.contains("filtered")) {
418
                    filterStates[filter] = false;
419
                    target.innerHTML = `<i class="fa fa-bars"></i> ${text.inactive}`;
420
                } else {
421
                    filterStates[filter] = true;
422
                    target.innerHTML = `<i class="fa fa-filter"></i> ${text.active}`;
423
                }
424
425
                bookings_table.DataTable().ajax.reload(() => {
426
                    bookings_table
427
                        .DataTable()
428
                        .column("status:name")
429
                        .visible(filterStates[filter], false);
430
                });
431
432
                target.classList.toggle("filtered");
433
            }
434
        });
448
        });
435
    </script>
449
    </script>
436
[% END %]
450
[% END %]
437
- 

Return to bug 40656