|
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 |
- |
|
|