841 lines
27 KiB
JavaScript
841 lines
27 KiB
JavaScript
/**
|
|
* Recursively collect changed entries from a Struct::Diff node.
|
|
* Returns an array of { key, before, after } objects.
|
|
* Nested diffs are flattened with dot-separated keys.
|
|
*/
|
|
function collectDiffEntries(node, prefix) {
|
|
var entries = [];
|
|
Object.keys(node).forEach(function (key) {
|
|
var change = node[key];
|
|
var fullKey = prefix ? prefix + "." + key : key;
|
|
if ("O" in change || "N" in change) {
|
|
entries.push({ key: fullKey, before: change.O, after: change.N });
|
|
} else if ("A" in change) {
|
|
entries.push({ key: fullKey, before: undefined, after: change.A });
|
|
} else if ("R" in change) {
|
|
entries.push({ key: fullKey, before: change.R, after: undefined });
|
|
} else if ("D" in change) {
|
|
entries = entries.concat(collectDiffEntries(change.D, fullKey));
|
|
}
|
|
});
|
|
return entries;
|
|
}
|
|
|
|
/**
|
|
* Pretty-print a parsed JSON value as nested <ul> markup. Arrays become
|
|
* ordered lists preserving MARC-in-JSON field order; objects render their
|
|
* keys. Used by both the struct-diff renderer and the catalog info renderer.
|
|
*/
|
|
function fmtJsonValue(v) {
|
|
if (v === undefined || v === null) return "";
|
|
if (typeof v !== "object") return $("<span>").text(String(v)).html();
|
|
if (Array.isArray(v)) {
|
|
var items = v
|
|
.map(function (item) {
|
|
return "<li>" + fmtJsonValue(item) + "</li>";
|
|
})
|
|
.join("");
|
|
return '<ul class="diff-obj">' + items + "</ul>";
|
|
}
|
|
var items = Object.keys(v)
|
|
.map(function (k) {
|
|
return (
|
|
"<li>" +
|
|
$("<span>").text(k).html() +
|
|
": " +
|
|
fmtJsonValue(v[k]) +
|
|
"</li>"
|
|
);
|
|
})
|
|
.join("");
|
|
return '<ul class="diff-obj">' + items + "</ul>";
|
|
}
|
|
|
|
/**
|
|
* Render the info column for a CATALOGUING or AUTHORITIES log entry.
|
|
* The payload is "biblio <JSON>" or "authority <JSON>" where the JSON
|
|
* is the pre-change (MODIFY) or final (DELETE) state of the record,
|
|
* including a MARC-in-JSON `_marc` key. Returns rendered HTML, or null
|
|
* if the text doesn't look like a record JSON payload (e.g. the bare
|
|
* "biblio" / "authority" prefix logged on ADD).
|
|
*/
|
|
function renderRecordInfo(raw) {
|
|
if (!raw) return null;
|
|
var match = raw.match(/^(?:biblio|authority)\s+(\{[\s\S]*\})\s*$/);
|
|
if (!match) return null;
|
|
var parsed;
|
|
try {
|
|
parsed = JSON.parse(match[1]);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
return '<div class="loginfo-struct">' + fmtJsonValue(parsed) + "</div>";
|
|
}
|
|
|
|
/* global override_labels */
|
|
|
|
/**
|
|
* Render the info column for a CIRCULATION or HOLDS log entry whose
|
|
* payload is a JSON object containing `forced` and/or `confirmations`
|
|
* arrays of override codes. Returns rendered HTML, or null if the
|
|
* payload doesn't look like a circulation JSON object.
|
|
*/
|
|
function renderCircInfo(raw) {
|
|
if (!raw) return null;
|
|
var trimmed = raw.replace(/^\s+|\s+$/g, "");
|
|
if (trimmed.charAt(0) !== "{") return null;
|
|
var parsed;
|
|
try {
|
|
parsed = JSON.parse(trimmed);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
if (!parsed || typeof parsed !== "object") return null;
|
|
|
|
var forced =
|
|
Array.isArray(parsed.forced) && parsed.forced.length
|
|
? parsed.forced
|
|
: null;
|
|
var confirmations =
|
|
Array.isArray(parsed.confirmations) && parsed.confirmations.length
|
|
? parsed.confirmations
|
|
: null;
|
|
if (!forced && !confirmations) return null;
|
|
|
|
function translate(code) {
|
|
return override_labels[code] || escapeHtml(code);
|
|
}
|
|
|
|
var html = "";
|
|
if (forced) {
|
|
html += forced
|
|
.map(function (c) {
|
|
return '<div class="forced">' + translate(c) + "</div>";
|
|
})
|
|
.join("");
|
|
}
|
|
if (confirmations) {
|
|
html += confirmations
|
|
.map(function (c) {
|
|
return '<div class="confirmed">' + translate(c) + "</div>";
|
|
})
|
|
.join("");
|
|
}
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Parse a log `info` payload as a JSON object, returning the parsed object
|
|
* or null when it isn't a JSON object.
|
|
*/
|
|
function parseInfoObject(raw) {
|
|
if (!raw) return null;
|
|
const trimmed = raw.replace(/^\s+|\s+$/g, "");
|
|
if (trimmed.charAt(0) !== "{") return null;
|
|
try {
|
|
const parsed = JSON.parse(trimmed);
|
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build the "Item <barcode>" link for a CIRCULATION log entry. The item is
|
|
* embedded on the row based on the itemnumber carried in `info`. For JSON
|
|
* payloads (ISSUE/RENEWAL) the item's branchcode is appended after the barcode,
|
|
* Returns the anchor markup wrapped in a <div>, or null when no item is
|
|
* embedded (e.g. the item has since been deleted).
|
|
*/
|
|
function renderCircItemLink(row) {
|
|
const item = row.item;
|
|
if (!item) return null;
|
|
|
|
const itemnumber = item.item_id;
|
|
const barcode = item.external_id != null ? item.external_id : itemnumber;
|
|
const biblionumber = item.biblio_id;
|
|
|
|
const parsed = parseInfoObject(row.info);
|
|
const branchcode = parsed && parsed.branchcode ? parsed.branchcode : null;
|
|
|
|
const encodedItem = encodeURIComponent(itemnumber);
|
|
const encodedBiblio = encodeURIComponent(biblionumber);
|
|
const label = branchcode
|
|
? __("Item %s %s").format(escapeHtml(barcode), escapeHtml(branchcode))
|
|
: __("Item %s").format(escapeHtml(barcode));
|
|
|
|
return (
|
|
"<div>" +
|
|
'<a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=' +
|
|
encodedItem +
|
|
"&biblionumber=" +
|
|
encodedBiblio +
|
|
"&bi=" +
|
|
encodedBiblio +
|
|
"#item" +
|
|
encodedItem +
|
|
'" title="' +
|
|
__("Display detail for this item") +
|
|
'">' +
|
|
label +
|
|
"</a>" +
|
|
"</div>"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Build the hold link (and optional item link) for a HOLDS log entry whose
|
|
* `info` payload is a JSON object carrying `hold` and `biblionumber`.
|
|
* Returns the markup wrapped in a <div>, or null.
|
|
*/
|
|
function renderHoldsInfoLink(row) {
|
|
const parsed = parseInfoObject(row.info);
|
|
if (!parsed || parsed.hold == null || parsed.biblionumber == null) {
|
|
return null;
|
|
}
|
|
|
|
const encodedBiblio = encodeURIComponent(parsed.biblionumber);
|
|
let html =
|
|
'<a href="/cgi-bin/koha/reserve/request.pl?biblionumber=' +
|
|
encodedBiblio +
|
|
'" title="' +
|
|
__("Display holds for this record") +
|
|
'">' +
|
|
__("Hold %s on biblio %s").format(
|
|
escapeHtml(parsed.hold),
|
|
escapeHtml(parsed.biblionumber)
|
|
) +
|
|
"</a>";
|
|
|
|
if (parsed.itemnumber != null) {
|
|
const encodedItem = encodeURIComponent(parsed.itemnumber);
|
|
html +=
|
|
" - " +
|
|
'<a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=' +
|
|
encodedItem +
|
|
"&biblionumber=" +
|
|
encodedBiblio +
|
|
"#item" +
|
|
encodedItem +
|
|
'" title="' +
|
|
__("Display detail for this item") +
|
|
'">' +
|
|
__("Item %s").format(escapeHtml(parsed.itemnumber)) +
|
|
"</a>";
|
|
}
|
|
|
|
return "<div>" + html + "</div>";
|
|
}
|
|
|
|
/**
|
|
* Render Struct::Diff JSON as a human-readable before/after table.
|
|
* Falls back to a <pre> block for non-Struct::Diff JSON or plain text.
|
|
*/
|
|
function renderStructDiff(raw) {
|
|
if (!raw) return "";
|
|
var diff;
|
|
try {
|
|
diff = JSON.parse(raw);
|
|
} catch (e) {
|
|
return $("<pre>").text(raw)[0].outerHTML;
|
|
}
|
|
if (!diff || !diff.D || typeof diff.D !== "object") {
|
|
return $("<pre>").text(raw)[0].outerHTML;
|
|
}
|
|
|
|
var entries = collectDiffEntries(diff.D, "");
|
|
if (!entries.length) return "";
|
|
|
|
var rows = entries
|
|
.map(function (e) {
|
|
var before =
|
|
e.before !== undefined
|
|
? "<del>" + fmtJsonValue(e.before) + "</del>"
|
|
: "";
|
|
var after =
|
|
e.after !== undefined
|
|
? "<ins>" + fmtJsonValue(e.after) + "</ins>"
|
|
: "";
|
|
return (
|
|
"<tr>" +
|
|
'<td class="diff-key">' +
|
|
$("<span>").text(e.key).html() +
|
|
"</td>" +
|
|
'<td class="diff-before">' +
|
|
before +
|
|
"</td>" +
|
|
'<td class="diff-after">' +
|
|
after +
|
|
"</td>" +
|
|
"</tr>"
|
|
);
|
|
})
|
|
.join("");
|
|
|
|
return (
|
|
'<table class="struct-diff">' +
|
|
"<thead><tr>" +
|
|
"<th>" +
|
|
__("Field") +
|
|
"</th>" +
|
|
"<th>" +
|
|
__("Before") +
|
|
"</th>" +
|
|
"<th>" +
|
|
__("After") +
|
|
"</th>" +
|
|
"</tr></thead>" +
|
|
"<tbody>" +
|
|
rows +
|
|
"</tbody>" +
|
|
"</table>"
|
|
);
|
|
}
|
|
|
|
function tickAll(section) {
|
|
$("input[type='checkbox'][name='" + section + "']").prop("checked", true);
|
|
$("#" + section.slice(0, -1) + "ALL").prop("checked", true);
|
|
$("input[type='checkbox'][name='" + section + "']").prop("disabled", true);
|
|
$("#" + section.slice(0, -1) + "ALL").prop("disabled", false);
|
|
}
|
|
|
|
function untickAll(section) {
|
|
$("input[type='checkbox'][name='" + section + "']").prop("checked", false);
|
|
$("input[type='checkbox'][name='" + section + "']").prop("disabled", false);
|
|
}
|
|
|
|
function limitCheckboxes() {
|
|
var limit = 2;
|
|
var compare_link =
|
|
'<a href="#" class="btn btn-link compare_link"><i class="fa fa-columns"></i> ' +
|
|
__("View comparison") +
|
|
"</a>";
|
|
$(document).on("change", ".compare", function () {
|
|
var checked = [];
|
|
$(".compare").each(function () {
|
|
if ($(this).prop("checked")) {
|
|
checked.push($(this).data("actionid"));
|
|
}
|
|
});
|
|
if (checked.length > 0) {
|
|
$("#select_none").removeClass("disabled");
|
|
} else {
|
|
$("#select_none").addClass("disabled");
|
|
}
|
|
if (checked.length == 2) {
|
|
$("a.compare_link").remove();
|
|
$("#compare_info" + checked[0]).prepend(compare_link);
|
|
$("#compare_info" + checked[1]).prepend(compare_link);
|
|
$("button.compare_link").removeClass("disabled");
|
|
} else if (checked.length > limit) {
|
|
humanMsg.displayAlert(
|
|
__("You can select maximum of two checkboxes")
|
|
);
|
|
$(this).prop("checked", false);
|
|
} else if (checked.length < limit) {
|
|
$("a.compare_link").remove();
|
|
$("button.compare_link").addClass("disabled");
|
|
}
|
|
});
|
|
}
|
|
|
|
/* global module_labels action_labels interface_labels */
|
|
|
|
function escapeHtml(str) {
|
|
if (str == null) return "";
|
|
return $("<span>").text(String(str)).html();
|
|
}
|
|
|
|
function renderObject(data, type, row) {
|
|
var mod = row.module;
|
|
var obj = row.object;
|
|
if (obj == null) return "";
|
|
|
|
if (
|
|
mod == "MEMBERS" ||
|
|
mod == "CIRCULATION" ||
|
|
mod == "FINES" ||
|
|
mod == "APIKEYS"
|
|
) {
|
|
var patron = row.patron;
|
|
var name;
|
|
if (patron && (patron.firstname || patron.surname)) {
|
|
name = escapeHtml(
|
|
[patron.firstname, patron.surname].filter(Boolean).join(" ")
|
|
);
|
|
}
|
|
if (mod == "APIKEYS") {
|
|
var label = name
|
|
? __("API keys for %s (%s)").format(name, escapeHtml(obj))
|
|
: __("API keys for patron %s").format(escapeHtml(obj));
|
|
return (
|
|
'<a href="/cgi-bin/koha/members/apikeys.pl?patron_id=' +
|
|
encodeURIComponent(obj) +
|
|
'">' +
|
|
label +
|
|
"</a>"
|
|
);
|
|
}
|
|
return (
|
|
'<a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=' +
|
|
encodeURIComponent(obj) +
|
|
'">' +
|
|
(name ? name + " (" + escapeHtml(obj) + ")" : escapeHtml(obj)) +
|
|
"</a>"
|
|
);
|
|
}
|
|
|
|
if (mod == "CATALOGUING") {
|
|
var info = row.info || "";
|
|
if (info.substr(0, 6) == "biblio") {
|
|
return (
|
|
'<a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=' +
|
|
encodeURIComponent(obj) +
|
|
'">' +
|
|
__("Bibliographic record %s").format(escapeHtml(obj)) +
|
|
"</a>"
|
|
);
|
|
}
|
|
return escapeHtml(obj);
|
|
}
|
|
|
|
if (mod == "SERIAL") {
|
|
return (
|
|
'<a href="/cgi-bin/koha/serials/subscription-detail.pl?subscriptionid=' +
|
|
encodeURIComponent(obj) +
|
|
'">' +
|
|
__("Subscription %s").format(escapeHtml(obj)) +
|
|
"</a>"
|
|
);
|
|
}
|
|
|
|
if (mod == "AUTHORITIES") {
|
|
return (
|
|
'<a href="/cgi-bin/koha/authorities/detail.pl?authid=' +
|
|
encodeURIComponent(obj) +
|
|
'">' +
|
|
__("Authority %s").format(escapeHtml(obj)) +
|
|
"</a>"
|
|
);
|
|
}
|
|
|
|
if (mod == "NOTICES") {
|
|
return escapeHtml(obj);
|
|
}
|
|
|
|
if (mod == "ACQUISITIONS" && row.action == "ACQUISITION ORDER" && obj) {
|
|
if (CAN_user_acquisition_order_manage) {
|
|
return __("Basket %s").format(
|
|
'<a href="/cgi-bin/koha/acqui/basket.pl?basketno=' +
|
|
encodeURIComponent(obj) +
|
|
'">' +
|
|
escapeHtml(obj) +
|
|
"</a>"
|
|
);
|
|
}
|
|
return __("Basket %s").format(escapeHtml(obj));
|
|
}
|
|
|
|
if (mod == "SUGGESTION") {
|
|
return (
|
|
'<a href="/cgi-bin/koha/suggestion/suggestion.pl?suggestionid=' +
|
|
encodeURIComponent(obj) +
|
|
'&op=show">' +
|
|
escapeHtml(obj) +
|
|
"</a>"
|
|
);
|
|
}
|
|
|
|
return escapeHtml(obj);
|
|
}
|
|
|
|
function renderInfo(data, type, row) {
|
|
var mod = row.module;
|
|
var info = row.info;
|
|
var action_id = row.action_id;
|
|
|
|
if (info == null) return "";
|
|
|
|
if (mod == "SYSTEMPREFERENCE" || mod == "REPORTS" || mod == "NEWS") {
|
|
var split_info = info.split(" | ");
|
|
var filter_value = escapeHtml(split_info[0]);
|
|
return (
|
|
'<div class="loginfo" id="loginfo' +
|
|
escapeHtml(action_id) +
|
|
'">' +
|
|
escapeHtml(info) +
|
|
"</div>" +
|
|
'<div class="compare_info" id="compare_info' +
|
|
escapeHtml(action_id) +
|
|
'"><label><input type="checkbox" name="diff" id="action_id' +
|
|
escapeHtml(action_id) +
|
|
'" data-actionid="' +
|
|
escapeHtml(action_id) +
|
|
'" data-filter="' +
|
|
filter_value +
|
|
'" class="compare" /> Compare</label></div>'
|
|
);
|
|
}
|
|
|
|
if (mod == "NOTICES") {
|
|
return (
|
|
'<div class="loginfo" id="loginfo' +
|
|
escapeHtml(action_id) +
|
|
'">' +
|
|
escapeHtml(info) +
|
|
"</div>" +
|
|
'<div class="compare_info" id="compare_info' +
|
|
escapeHtml(action_id) +
|
|
'"><label><input type="checkbox" name="diff" id="action_id' +
|
|
escapeHtml(action_id) +
|
|
'" data-actionid="' +
|
|
escapeHtml(action_id) +
|
|
'" data-filter="' +
|
|
escapeHtml(row.object || "") +
|
|
'" class="compare" /> Compare</label></div>'
|
|
);
|
|
}
|
|
|
|
let prefix = "";
|
|
let body = null;
|
|
if (mod == "CIRCULATION") {
|
|
prefix = renderCircItemLink(row) || "";
|
|
body = renderCircInfo(info);
|
|
} else if (mod == "HOLDS") {
|
|
prefix = renderHoldsInfoLink(row) || "";
|
|
body = renderCircInfo(info);
|
|
}
|
|
// Only fall back to the record/plain-text renderers when we produced
|
|
// neither a link nor any override text, so we never dump the raw JSON
|
|
// payload alongside a rendered item/hold link.
|
|
if (body === null && prefix === "") {
|
|
body = renderRecordInfo(info);
|
|
}
|
|
if (body === null && prefix === "") {
|
|
body = escapeHtml(info);
|
|
}
|
|
return (
|
|
'<div class="loginfo" id="loginfo' +
|
|
escapeHtml(action_id) +
|
|
'">' +
|
|
prefix +
|
|
(body || "") +
|
|
"</div>"
|
|
);
|
|
}
|
|
|
|
function buildApiFilters(params) {
|
|
var filters = {};
|
|
|
|
if (params.user) {
|
|
filters.user = params.user;
|
|
}
|
|
|
|
// Cataloguing modification log: object_type=biblio means we should
|
|
// surface biblio-level changes ("biblio%") on the biblionumber AND
|
|
// item-level changes ("item%") on each of the biblio's itemnumbers
|
|
// (computed server-side and passed in via biblio_itemnumbers).
|
|
var biblio_expansion = false;
|
|
if (
|
|
params.object &&
|
|
params.object_type === "biblio" &&
|
|
params.modules &&
|
|
params.modules.length === 1 &&
|
|
params.modules[0] === "CATALOGUING"
|
|
) {
|
|
var itemnumbers = params.biblio_itemnumbers || [];
|
|
var or_clauses = [{ object: params.object, info: { like: "biblio%" } }];
|
|
if (itemnumbers.length) {
|
|
or_clauses.push({
|
|
object: itemnumbers,
|
|
info: { like: "item%" },
|
|
});
|
|
}
|
|
filters["-or"] = or_clauses;
|
|
biblio_expansion = true;
|
|
} else if (params.object) {
|
|
filters.object = params.object;
|
|
}
|
|
|
|
if (params.info && !biblio_expansion) {
|
|
filters.info = { like: "%" + params.info + "%" };
|
|
}
|
|
|
|
if (params.modules && params.modules.length > 0) {
|
|
var non_empty = params.modules.filter(function (m) {
|
|
return m !== "";
|
|
});
|
|
if (non_empty.length > 0) {
|
|
filters.module = non_empty;
|
|
}
|
|
}
|
|
|
|
if (params.actions && params.actions.length > 0) {
|
|
var non_empty_actions = params.actions.filter(function (a) {
|
|
return a !== "";
|
|
});
|
|
if (non_empty_actions.length > 0) {
|
|
// Circulation uses RENEWAL, but Patrons uses RENEW
|
|
if (non_empty_actions.indexOf("RENEW") !== -1) {
|
|
non_empty_actions.push("RENEWAL");
|
|
}
|
|
filters.action = non_empty_actions;
|
|
}
|
|
}
|
|
|
|
if (params.interfaces && params.interfaces.length > 0) {
|
|
var non_empty_ifaces = params.interfaces.filter(function (i) {
|
|
return i !== "";
|
|
});
|
|
if (non_empty_ifaces.length > 0) {
|
|
filters.interface = non_empty_ifaces;
|
|
}
|
|
}
|
|
|
|
// Flatpickr always submits dates in YYYY-MM-DD format (dateFormat: "Y-m-d")
|
|
if (params.datefrom && params.dateto) {
|
|
filters.timestamp = {
|
|
">=": params.datefrom + "T00:00:00Z",
|
|
"<=": params.dateto + "T23:59:59Z",
|
|
};
|
|
} else if (params.datefrom) {
|
|
filters.timestamp = {
|
|
">=": params.datefrom + "T00:00:00Z",
|
|
};
|
|
} else if (params.dateto) {
|
|
filters.timestamp = {
|
|
"<=": params.dateto + "T23:59:59Z",
|
|
};
|
|
}
|
|
|
|
return filters;
|
|
}
|
|
|
|
$(document).ready(function () {
|
|
limitCheckboxes();
|
|
|
|
if ($('input[type="checkbox"][name="modules"]:checked').length == 0) {
|
|
tickAll("modules");
|
|
}
|
|
$("#moduleALL").change(function () {
|
|
if (this.checked == true) {
|
|
tickAll("modules");
|
|
} else {
|
|
untickAll("modules");
|
|
}
|
|
});
|
|
$("input[type='checkbox'][name='modules']").change(function () {
|
|
if (
|
|
$("input[name='modules']:checked").length ==
|
|
$("input[name='modules']").length - 1
|
|
) {
|
|
tickAll("modules");
|
|
}
|
|
});
|
|
|
|
if ($('input[name="actions"]:checked').length == 0) {
|
|
tickAll("actions");
|
|
}
|
|
$("#actionALL").change(function () {
|
|
if (this.checked == true) {
|
|
tickAll("actions");
|
|
} else {
|
|
untickAll("actions");
|
|
}
|
|
});
|
|
$("input[name='actions']").change(function () {
|
|
if (
|
|
$("input[name='actions']:checked").length ==
|
|
$("input[name='actions']").length - 1
|
|
) {
|
|
tickAll("actions");
|
|
}
|
|
});
|
|
|
|
if ($('input[name="interfaces"]:checked').length == 0) {
|
|
tickAll("interfaces");
|
|
}
|
|
$("#interfaceALL").change(function () {
|
|
if (this.checked == true) {
|
|
tickAll("interfaces");
|
|
} else {
|
|
untickAll("interfaces");
|
|
}
|
|
});
|
|
$("input[name='interfaces']").change(function () {
|
|
if (
|
|
$("input[name='interfaces']:checked").length ==
|
|
$("input[name='interfaces']").length - 1
|
|
) {
|
|
tickAll("interfaces");
|
|
}
|
|
});
|
|
|
|
if (do_it && typeof search_params !== "undefined") {
|
|
var api_filters = buildApiFilters(search_params);
|
|
|
|
$("#logst").kohaTable(
|
|
{
|
|
ajax: {
|
|
url: "/api/v1/action_logs",
|
|
},
|
|
embed: ["librarian", "patron", "item"],
|
|
order: [[0, "desc"]],
|
|
pagingType: "full",
|
|
autoWidth: false,
|
|
columns: [
|
|
{
|
|
data: "timestamp",
|
|
title: __("Date"),
|
|
searchable: true,
|
|
orderable: true,
|
|
render: function (data, type, row) {
|
|
if (type == "sort" || type == "type") {
|
|
return data;
|
|
}
|
|
return $datetime(data);
|
|
},
|
|
},
|
|
{
|
|
data: "user",
|
|
title: __("Librarian"),
|
|
searchable: true,
|
|
orderable: true,
|
|
render: function (data, type, row) {
|
|
if (!data) return "";
|
|
var librarian = row.librarian;
|
|
var label;
|
|
if (
|
|
librarian &&
|
|
(librarian.firstname || librarian.surname)
|
|
) {
|
|
label =
|
|
escapeHtml(
|
|
[librarian.firstname, librarian.surname]
|
|
.filter(Boolean)
|
|
.join(" ")
|
|
) +
|
|
" (" +
|
|
escapeHtml(data) +
|
|
")";
|
|
} else {
|
|
label = escapeHtml(data);
|
|
}
|
|
return (
|
|
'<a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=' +
|
|
encodeURIComponent(data) +
|
|
'" title="' +
|
|
__("Display detail for this librarian") +
|
|
'">' +
|
|
label +
|
|
"</a>"
|
|
);
|
|
},
|
|
},
|
|
{
|
|
data: "module",
|
|
title: __("Module"),
|
|
searchable: true,
|
|
orderable: true,
|
|
render: function (data, type, row) {
|
|
return module_labels[data] || escapeHtml(data);
|
|
},
|
|
},
|
|
{
|
|
data: "action",
|
|
title: __("Action"),
|
|
searchable: true,
|
|
orderable: true,
|
|
render: function (data, type, row) {
|
|
return action_labels[data] || escapeHtml(data);
|
|
},
|
|
},
|
|
{
|
|
data: "object",
|
|
title: __("Object"),
|
|
searchable: true,
|
|
orderable: true,
|
|
render: renderObject,
|
|
},
|
|
{
|
|
data: "info",
|
|
title: __("Info"),
|
|
searchable: true,
|
|
orderable: false,
|
|
render: renderInfo,
|
|
},
|
|
{
|
|
data: "interface",
|
|
title: __("Interface"),
|
|
searchable: true,
|
|
orderable: true,
|
|
render: function (data, type, row) {
|
|
if (!data) return "";
|
|
return (
|
|
interface_labels[data.toUpperCase()] ||
|
|
escapeHtml(data)
|
|
);
|
|
},
|
|
},
|
|
{
|
|
data: "diff",
|
|
title: __("Diff"),
|
|
searchable: false,
|
|
orderable: false,
|
|
render: function (data, type, row) {
|
|
if (!data) return "";
|
|
return renderStructDiff(data);
|
|
},
|
|
},
|
|
],
|
|
},
|
|
table_settings,
|
|
0,
|
|
api_filters
|
|
);
|
|
}
|
|
|
|
$("body").on("click", ".compare_link", function (e) {
|
|
e.preventDefault();
|
|
if ($(this).hasClass("disabled")) {
|
|
humanMsg.displayAlert(__("You must select two entries to compare"));
|
|
} else {
|
|
var firstid = $(".compare:checked").eq(0).data("actionid");
|
|
var secondid = $(".compare:checked").eq(1).data("actionid");
|
|
var firstvalue = $("#loginfo" + firstid).text();
|
|
var secondvalue = $("#loginfo" + secondid).text();
|
|
var diffs = diffString(secondvalue, firstvalue);
|
|
$("#col1 pre,#col2 pre").html(diffs);
|
|
$("#compareInfo").modal("show");
|
|
}
|
|
});
|
|
$("#compareInfo").on("hidden.bs.modal", function () {
|
|
$("#col1 pre,#col2 pre").html("");
|
|
});
|
|
|
|
$("#select_none").on("click", function (e) {
|
|
e.preventDefault();
|
|
$(".compare:checked").prop("checked", false).change();
|
|
});
|
|
|
|
patron_autocomplete($("#user"), {
|
|
"on-select-callback": function (event, ui) {
|
|
$("#user").val(ui.item.patron_id);
|
|
return false;
|
|
},
|
|
});
|
|
|
|
$(".log-disabled")
|
|
.each(function () {
|
|
if (CAN_user_parameters_manage_sysprefs) {
|
|
let pref = $(this).data("log");
|
|
url =
|
|
"/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=";
|
|
$(this).wrap(
|
|
"<a href='" + url + pref + "' target='_blank'></a>"
|
|
);
|
|
}
|
|
})
|
|
.tooltip();
|
|
});
|