Bugzilla – Attachment 191102 Details for
Bug 41565
Tidy kohaTable block - bookings
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 41565: bookings - auto tidy
9515333.patch (text/plain), 26.91 KB, created by
Jonathan Druart
on 2026-01-09 14:45:40 UTC
(
hide
)
Description:
Bug 41565: bookings - auto tidy
Filename:
MIME Type:
Creator:
Jonathan Druart
Created:
2026-01-09 14:45:40 UTC
Size:
26.91 KB
patch
obsolete
>From 95153335dcc5b329aea13bfa38f01903012aa5c7 Mon Sep 17 00:00:00 2001 >From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >Date: Fri, 9 Jan 2026 13:10:56 +0100 >Subject: [PATCH] Bug 41565: bookings - auto tidy > >--- > .../prog/en/modules/bookings/list.tt | 403 +++++++++--------- > 1 file changed, 201 insertions(+), 202 deletions(-) > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt >index ce36863d1c3..d1d14b38cb7 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt >@@ -77,26 +77,27 @@ > }); > </script> > <script> >- $(document).ready(function(){ >- >+ $(document).ready(function () { > var items = $.ajax({ >- url: '/api/v1/biblios/%s/items?bookable=1'.format(biblionumber), >- dataType: 'json', >- type: 'GET' >+ url: "/api/v1/biblios/%s/items?bookable=1".format(biblionumber), >+ dataType: "json", >+ type: "GET", > }); > var bookings = $.ajax({ >- url: '/api/v1/biblios/%s/bookings?_per_page=-1'.format(biblionumber), >- headers: { 'x-koha-embed': 'patron' }, >- dataType: 'json', >- type: 'GET', >+ url: "/api/v1/biblios/%s/bookings?_per_page=-1".format(biblionumber), >+ headers: { "x-koha-embed": "patron" }, >+ dataType: "json", >+ type: "GET", > }); > > $.when(items, bookings).then( >- function(items, bookings){ >- var itemsSet = new vis.DataSet([{ >- id: 0, >- content: _("Record level") >- }]); >+ function (items, bookings) { >+ var itemsSet = new vis.DataSet([ >+ { >+ id: 0, >+ content: _("Record level"), >+ }, >+ ]); > for (item of items[0]) { > itemsSet.add({ > id: item.item_id, >@@ -105,11 +106,11 @@ > } > > var bookingsSet = new vis.DataSet(); >- for (booking of bookings[0]){ >+ for (booking of bookings[0]) { > const isActive = ["new", "pending", "active"].includes(booking.status); > const patronContent = $patron_to_html(booking.patron, { > display_cardnumber: true, >- url: false >+ url: false, > }); > bookingsSet.add({ > id: booking.booking_id, >@@ -120,25 +121,22 @@ > end: dayjs(booking.end_date).toDate(), > content: !isActive ? `<s>${patronContent}</s>` : patronContent, > className: booking.status === "cancelled" ? "cancelled" : "", >- ...(permissions.CAN_user_circulate_manage_bookings >- ? {editable: !["cancelled", "completed"].includes(booking.status) ? { remove: true, updateTime: true } : false} >- : {editable: false} >- ), >- type: 'range', >+ ...(permissions.CAN_user_circulate_manage_bookings ? { editable: !["cancelled", "completed"].includes(booking.status) ? { remove: true, updateTime: true } : false } : { editable: false }), >+ type: "range", > group: booking.item_id ?? 0, > }); > } > >- var container = document.getElementById('bookings-timeline'); >+ var container = document.getElementById("bookings-timeline"); > var options = { > stack: true, > verticalScroll: true, > orientation: { > axis: "both", >- item: "top" >+ item: "top", > }, >- timeAxis: {scale: 'day', step: 1}, >- dataAttributes: [ 'booking' ], >+ timeAxis: { scale: "day", step: 1 }, >+ dataAttributes: ["booking"], > > // always snap to full days, independent of the scale > snap: function (date, scale, step) { >@@ -151,15 +149,15 @@ > // prevent overlapping bookings > onMoving: function (item, callback) { > var overlapping = bookingsSet.get({ >- filter: function(testItem) { >+ filter: function (testItem) { > if (testItem.id == item.id) { > return false; > } > if (testItem.group != item.group) { > return false; > } >- return ((item.start < testItem.end) && (item.end > testItem.start)); >- } >+ return item.start < testItem.end && item.end > testItem.start; >+ }, > }); > > if (overlapping.length == 0) { >@@ -172,10 +170,29 @@ > let startDate = dayjs(data.start); > > // set end datetime hours and minutes to the end of the day >- let endDate = dayjs(data.end).endOf('day'); >+ let endDate = dayjs(data.end).endOf("day"); > >- $('#placeBookingModal').modal('show', $('<button data-booking="'+data.id+'" data-biblionumber="' + biblionumber + '" data-itemnumber="'+data.group+'" data-patron="'+data.patron+'" data-pickup_library="'+data.pickup_library+'" data-start_date="'+startDate.toISOString()+'" data-end_date="'+endDate.toISOString()+'">')); >- $('#placeBookingModal').on('hide.bs.modal', function(e) { >+ $("#placeBookingModal").modal( >+ "show", >+ $( >+ '<button data-booking="' + >+ data.id + >+ '" data-biblionumber="' + >+ biblionumber + >+ '" data-itemnumber="' + >+ data.group + >+ '" data-patron="' + >+ data.patron + >+ '" data-pickup_library="' + >+ data.pickup_library + >+ '" data-start_date="' + >+ startDate.toISOString() + >+ '" data-end_date="' + >+ endDate.toISOString() + >+ '">' >+ ) >+ ); >+ $("#placeBookingModal").on("hide.bs.modal", function (e) { > if (update_success) { > update_success = 0; > callback(data); >@@ -185,10 +202,10 @@ > }); > }, > onRemove: function (item, callback) { >- const button = document.createElement('button'); >+ const button = document.createElement("button"); > button.dataset.booking = item.booking || item.id; >- $('#cancelBookingModal').modal('show', button); >- $('#cancelBookingModal').on('hide.bs.modal', function(e) { >+ $("#cancelBookingModal").modal("show", button); >+ $("#cancelBookingModal").on("hide.bs.modal", function (e) { > if (cancel_success) { > cancel_success = 0; > callback(item); >@@ -196,11 +213,11 @@ > callback(null); > } > }); >- } >+ }, > }; > timeline = new vis.Timeline(container, bookingsSet, itemsSet, options); > }, >- function(jqXHR, textStatus, errorThrown){ >+ function (jqXHR, textStatus, errorThrown) { > console.log("Fetch failed"); > } > ); >@@ -211,7 +228,7 @@ > }) > .build({ > end_date: ({ filters, isNotApplied }) => { >- if (isNotApplied(filters['filter-expired'])) { >+ if (isNotApplied(filters["filter-expired"])) { > let today = new Date(); > return { ">=": today.toISOString() }; > } >@@ -219,156 +236,143 @@ > status: ({ filters, isNotApplied }) => { > const defaults = ["new", "pending", "active"]; > const filtered = [...defaults]; >- if (isNotApplied(filters['filter-cancelled'])) { >+ if (isNotApplied(filters["filter-cancelled"])) { > filtered.push("cancelled"); > } >- if (isNotApplied(filters['filter-completed'])) { >+ if (isNotApplied(filters["filter-completed"])) { > filtered.push("completed"); > } > return { "-in": filtered }; >- } >+ }, > }); > >- > var bookings_table_url = "/api/v1/biblios/%s/bookings".format(biblionumber); >- bookings_table = $('#bookings_table').kohaTable({ >- ajax: { >- "url": bookings_table_url >- }, >- embed: [ >- "item", >- "patron", >- "pickup_library" >- ], >- columns: [{ >- data: "booking_id", >- title: _("Booking ID"), >- visible: false >- }, >+ bookings_table = $("#bookings_table").kohaTable( > { >- data: "", >- title: _("Status"), >- name: "status", >- searchable: false, >- orderable: false, >- visible: true, >- render: function (data, type, row, meta) { >- const isExpired = date => dayjs(date).isBefore(new Date()); >- const isActive = (startDate, endDate) => { >- const now = dayjs(); >- return ( >- now.isAfter(dayjs(startDate)) && >- now.isBefore(dayjs(endDate).add(1, "day")) >- ); >- }; >+ ajax: { >+ url: bookings_table_url, >+ }, >+ embed: ["item", "patron", "pickup_library"], >+ columns: [ >+ { >+ data: "booking_id", >+ title: _("Booking ID"), >+ visible: false, >+ }, >+ { >+ data: "", >+ title: _("Status"), >+ name: "status", >+ searchable: false, >+ orderable: false, >+ visible: true, >+ render: function (data, type, row, meta) { >+ const isExpired = date => dayjs(date).isBefore(new Date()); >+ const isActive = (startDate, endDate) => { >+ const now = dayjs(); >+ return now.isAfter(dayjs(startDate)) && now.isBefore(dayjs(endDate).add(1, "day")); >+ }; > >- const statusMap = { >- new: () => { >- if (isExpired(row.end_date)) { >- return _("Expired"); >- } >+ const statusMap = { >+ new: () => { >+ if (isExpired(row.end_date)) { >+ return _("Expired"); >+ } > >- if (isActive(row.start_date, row.end_date)) { >- return _("Active"); >- } >+ if (isActive(row.start_date, row.end_date)) { >+ return _("Active"); >+ } > >- if (dayjs(row.start_date).isAfter(new Date())) { >- return _("Pending"); >- } >+ if (dayjs(row.start_date).isAfter(new Date())) { >+ return _("Pending"); >+ } > >- return _("New"); >- }, >- cancelled: () => >- [_("Cancelled"), row.cancellation_reason] >- .filter(Boolean) >- .join(": "), >- completed: () => _("Completed"), >- }; >+ return _("New"); >+ }, >+ cancelled: () => [_("Cancelled"), row.cancellation_reason].filter(Boolean).join(": "), >+ completed: () => _("Completed"), >+ }; > >- const statusText = statusMap[row.status] >- ? statusMap[row.status]() >- : _("Unknown"); >+ const statusText = statusMap[row.status] ? statusMap[row.status]() : _("Unknown"); > >- const classMap = [ >- { status: _("Expired"), class: "bg-secondary" }, >- { status: _("Cancelled"), class: "bg-secondary" }, >- { status: _("Pending"), class: "bg-warning" }, >- { status: _("Active"), class: "bg-primary" }, >- { status: _("Completed"), class: "bg-info" }, >- { status: _("New"), class: "bg-success" }, >- ]; >+ const classMap = [ >+ { status: _("Expired"), class: "bg-secondary" }, >+ { status: _("Cancelled"), class: "bg-secondary" }, >+ { status: _("Pending"), class: "bg-warning" }, >+ { status: _("Active"), class: "bg-primary" }, >+ { status: _("Completed"), class: "bg-info" }, >+ { status: _("New"), class: "bg-success" }, >+ ]; > >- const badgeClass = >- classMap.find(mapping => statusText.startsWith(mapping.status)) >- ?.class || "bg-secondary"; >+ const badgeClass = classMap.find(mapping => statusText.startsWith(mapping.status))?.class || "bg-secondary"; > >- return `<span class="badge rounded-pill ${badgeClass}">${statusText}</span>`; >- } >- }, >- { >- data: "item.external_id", >- title: _("Item"), >- searchable: true, >- orderable: true, >- defaultContent: _("Any item"), >- render: function(data,type,row,meta) { >- if ( row.item ) { >- return row.item.external_id + " (" + row.booking_id + ")"; >- } else { >- return null; >- } >- } >- }, >- { >- data: "patron.firstname:patron.surname", >- title: _("Patron"), >- searchable: true, >- orderable: true, >- render: function(data, type, row, meta) { >- return $patron_to_html(row.patron, { >- display_cardnumber: true, >- url: true >- }); >- } >- }, >- { >- data: "pickup_library.name", >- title: _("Pickup library"), >- searchable: true, >- orderable: true, >- render: function(data, type, row, meta) { >- return row.pickup_library.name; >- } >- }, >- { >- data: "start_date", >- title: _("Start date"), >- searchable: true, >- orderable: true, >- render: function(data, type, row, meta) { >- return $date(row.start_date); >- } >- }, >- { >- data: "end_date", >- title: _("End date"), >- searchable: true, >- orderable: true, >- render: function(data, type, row, meta) { >- return $date(row.end_date); >- } >- }, >- { >- data: "", >- title: _("Actions"), >- class:"actions", >- searchable: false, >- orderable: false, >- render: function(data, type, row, meta) { >- let result = ""; >- const is_readonly = ["cancelled", "completed"].includes(row.status); >- if (permissions.CAN_user_circulate_manage_bookings && !is_readonly) { >- result += ` >+ return `<span class="badge rounded-pill ${badgeClass}">${statusText}</span>`; >+ }, >+ }, >+ { >+ data: "item.external_id", >+ title: _("Item"), >+ searchable: true, >+ orderable: true, >+ defaultContent: _("Any item"), >+ render: function (data, type, row, meta) { >+ if (row.item) { >+ return row.item.external_id + " (" + row.booking_id + ")"; >+ } else { >+ return null; >+ } >+ }, >+ }, >+ { >+ data: "patron.firstname:patron.surname", >+ title: _("Patron"), >+ searchable: true, >+ orderable: true, >+ render: function (data, type, row, meta) { >+ return $patron_to_html(row.patron, { >+ display_cardnumber: true, >+ url: true, >+ }); >+ }, >+ }, >+ { >+ data: "pickup_library.name", >+ title: _("Pickup library"), >+ searchable: true, >+ orderable: true, >+ render: function (data, type, row, meta) { >+ return row.pickup_library.name; >+ }, >+ }, >+ { >+ data: "start_date", >+ title: _("Start date"), >+ searchable: true, >+ orderable: true, >+ render: function (data, type, row, meta) { >+ return $date(row.start_date); >+ }, >+ }, >+ { >+ data: "end_date", >+ title: _("End date"), >+ searchable: true, >+ orderable: true, >+ render: function (data, type, row, meta) { >+ return $date(row.end_date); >+ }, >+ }, >+ { >+ data: "", >+ title: _("Actions"), >+ class: "actions", >+ searchable: false, >+ orderable: false, >+ render: function (data, type, row, meta) { >+ let result = ""; >+ const is_readonly = ["cancelled", "completed"].includes(row.status); >+ if (permissions.CAN_user_circulate_manage_bookings && !is_readonly) { >+ result += ` > <button type="button" class="btn btn-default btn-xs edit-action" > data-bs-toggle="modal" > data-bs-target="#placeBookingModal" >@@ -384,17 +388,17 @@ > <i class="fa fa-pencil" aria-hidden="true"></i> %s > </button> > `.format( >- escape_str(row.booking_id), >- escape_str(biblionumber), >- escape_str(row.item_id), >- escape_str(row.patron_id), >- escape_str(row.pickup_library_id), >- escape_str(row.start_date), >- escape_str(row.end_date), >- escape_str(row.item.item_type_id), >- _("Edit") >- ); >- result += ` >+ escape_str(row.booking_id), >+ escape_str(biblionumber), >+ escape_str(row.item_id), >+ escape_str(row.patron_id), >+ escape_str(row.pickup_library_id), >+ escape_str(row.start_date), >+ escape_str(row.end_date), >+ escape_str(row.item.item_type_id), >+ _("Edit") >+ ); >+ result += ` > <button type="button" class="btn btn-default btn-xs cancel-action" > data-bs-toggle="modal" > data-bs-target="#cancelBookingModal" >@@ -402,12 +406,9 @@ > > > <i class="fa fa-trash" aria-hidden="true"></i> %s > </button> >- `.format( >- escape_str(row.booking_id), >- _("Cancel") >- ); >- const csrf_token = $('meta[name="csrf-token"]').attr("content"); >- result += ` >+ `.format(escape_str(row.booking_id), _("Cancel")); >+ const csrf_token = $('meta[name="csrf-token"]').attr("content"); >+ result += ` > <form name="checkout-transform" method="post" action="/cgi-bin/koha/circ/circulation.pl?borrowernumber=%s"> > <input type="hidden" name="csrf_token" value="${csrf_token}" /> > <input type="hidden" name="op" value="cud-checkout"/> >@@ -418,25 +419,23 @@ > <i class="fa fa-check-circle" aria-hidden="true"></i> %s > </button> > </form> >- `.format( >- escape_str(row.patron_id), >- escape_str(row.patron_id), >- escape_str(row.item.external_id), >- escape_str(row.end_date), >- _("Transform to checkout") >- ); >- } >- return result; >- } >- }] >- }, undefined, 0, additional_filters); >+ `.format(escape_str(row.patron_id), escape_str(row.patron_id), escape_str(row.item.external_id), escape_str(row.end_date), _("Transform to checkout")); >+ } >+ return result; >+ }, >+ }, >+ ], >+ }, >+ undefined, >+ 0, >+ additional_filters >+ ); > > $("#cancellation-reason").comboBox({ >- displayProperty: 'name', >+ displayProperty: "name", > placeholder: _("Select or type a reason"), >- context: 'modal', >+ context: "modal", > }); >- > }); > </script> > [% END %] >-- >2.43.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 41565
:
191101
|
191102
|
191347
|
191348