|
Lines 2368-2374
Link Here
|
| 2368 |
$(".delete-comment").on("click", function(){ |
2368 |
$(".delete-comment").on("click", function(){ |
| 2369 |
return confirm( _("Are you sure you want to delete this comment?") ); |
2369 |
return confirm( _("Are you sure you want to delete this comment?") ); |
| 2370 |
}); |
2370 |
}); |
|
|
2371 |
|
| 2372 |
[% IF Koha.Preference('UseDisplayModule') && CAN_user_displays %] |
| 2373 |
// Display functionality |
| 2374 |
let availableDisplays = []; |
| 2375 |
let displayItemsTable = null; |
| 2376 |
let selectedDisplayItems = []; |
| 2377 |
|
| 2378 |
// Load displays on page load |
| 2379 |
$(document).ready(function() { |
| 2380 |
// Load available displays for dropdown |
| 2381 |
$.ajax({ |
| 2382 |
url: '/api/v1/displays', |
| 2383 |
method: 'GET', |
| 2384 |
headers: { 'x-koha-embed': 'display_items' }, |
| 2385 |
success: function(displays) { |
| 2386 |
availableDisplays = displays.filter(display => display.enabled); |
| 2387 |
updateDisplayDropdown(); |
| 2388 |
updateDisplaySelect(); |
| 2389 |
}, |
| 2390 |
error: function() { |
| 2391 |
$('#display-dropdown').html('<li class="text-center text-muted">Failed to load displays</li>'); |
| 2392 |
} |
| 2393 |
}); |
| 2394 |
}); |
| 2395 |
|
| 2396 |
function updateDisplayDropdown() { |
| 2397 |
const dropdown = $('#display-dropdown'); |
| 2398 |
if (availableDisplays.length === 0) { |
| 2399 |
dropdown.html('<li class="dropdown-item text-muted">No active displays available</li>'); |
| 2400 |
return; |
| 2401 |
} |
| 2402 |
|
| 2403 |
let html = ''; |
| 2404 |
availableDisplays.forEach(display => { |
| 2405 |
html += `<li><a class="dropdown-item" href="#" data-display-id="${display.display_id}" onclick="openDisplayModal(${display.display_id})">${display.display_name}</a></li>`; |
| 2406 |
}); |
| 2407 |
dropdown.html(html); |
| 2408 |
} |
| 2409 |
|
| 2410 |
function updateDisplaySelect() { |
| 2411 |
// Update display select |
| 2412 |
const displaySelect = $('#display-select'); |
| 2413 |
let selectHtml = '<option value="">Select a display</option>'; |
| 2414 |
availableDisplays.forEach(display => { |
| 2415 |
selectHtml += `<option value="${display.display_id}">${display.display_name}</option>`; |
| 2416 |
}); |
| 2417 |
displaySelect.html(selectHtml); |
| 2418 |
} |
| 2419 |
|
| 2420 |
function initializeDisplayItemsTable() { |
| 2421 |
if (displayItemsTable && displayItemsTable.DataTable) { |
| 2422 |
displayItemsTable.DataTable().destroy(); |
| 2423 |
displayItemsTable = null; |
| 2424 |
} |
| 2425 |
|
| 2426 |
selectedDisplayItems = []; |
| 2427 |
|
| 2428 |
// Use the same API endpoint and embed options as the main items table |
| 2429 |
let item_table_url = "/api/v1/biblios/[% biblionumber | uri %]/items?"; |
| 2430 |
let embed = ["+strings,_status,home_library,holding_library"]; |
| 2431 |
|
| 2432 |
displayItemsTable = $("#display-items-table").kohaTable({ |
| 2433 |
ajax: { url: item_table_url }, |
| 2434 |
embed: embed, |
| 2435 |
order: [[1, 'asc']], // Sort by barcode |
| 2436 |
autoWidth: false, |
| 2437 |
paging: false, |
| 2438 |
searching: true, |
| 2439 |
info: false, |
| 2440 |
columns: [ |
| 2441 |
{ |
| 2442 |
data: "me.item_id", |
| 2443 |
searchable: false, |
| 2444 |
orderable: false, |
| 2445 |
render: function (data, type, row, meta) { |
| 2446 |
return `<input type="checkbox" class="display-item-checkbox" value="${row.item_id}" data-item-id="${row.item_id}">`; |
| 2447 |
} |
| 2448 |
}, |
| 2449 |
{ |
| 2450 |
data: "me.external_id", |
| 2451 |
searchable: true, |
| 2452 |
orderable: true, |
| 2453 |
render: function (data, type, row, meta) { |
| 2454 |
return escape_str(row.external_id || 'No barcode'); |
| 2455 |
} |
| 2456 |
}, |
| 2457 |
{ |
| 2458 |
data: "me.holding_library_id", |
| 2459 |
searchable: true, |
| 2460 |
orderable: true, |
| 2461 |
render: function (data, type, row, meta) { |
| 2462 |
return escape_str(row._strings.holding_library_id ? row._strings.holding_library_id.str : row.holding_library_id); |
| 2463 |
} |
| 2464 |
}, |
| 2465 |
{ |
| 2466 |
data: "me.home_library_id", |
| 2467 |
searchable: true, |
| 2468 |
orderable: true, |
| 2469 |
render: function (data, type, row, meta) { |
| 2470 |
return escape_str(row._strings.home_library_id ? row._strings.home_library_id.str : row.home_library_id); |
| 2471 |
} |
| 2472 |
}, |
| 2473 |
{ |
| 2474 |
[% IF Koha.Preference('UseDisplayModule') %] |
| 2475 |
data: "me.effective_location", |
| 2476 |
searchable: false, |
| 2477 |
orderable: false, |
| 2478 |
[% ELSE %] |
| 2479 |
data: "me.location", |
| 2480 |
searchable: true, |
| 2481 |
orderable: true, |
| 2482 |
[% END %] |
| 2483 |
render: function (data, type, row, meta) { |
| 2484 |
[% IF Koha.Preference('UseDisplayModule') %] |
| 2485 |
// Get the display name for effective location |
| 2486 |
let effective_loc_str = av_loc.get(row.effective_location) || row.effective_location; |
| 2487 |
return escape_str(effective_loc_str || ''); |
| 2488 |
[% ELSE %] |
| 2489 |
let loc_str = row._strings.location ? row._strings.location.str : row.location; |
| 2490 |
return escape_str(loc_str || ''); |
| 2491 |
[% END %] |
| 2492 |
} |
| 2493 |
}, |
| 2494 |
{ |
| 2495 |
data: "me.collection_code", |
| 2496 |
searchable: true, |
| 2497 |
orderable: true, |
| 2498 |
render: function (data, type, row, meta) { |
| 2499 |
return escape_str(row._strings.collection_code ? row._strings.collection_code.str : row.collection_code); |
| 2500 |
} |
| 2501 |
}, |
| 2502 |
{ |
| 2503 |
data: "me.callnumber", |
| 2504 |
searchable: true, |
| 2505 |
orderable: true, |
| 2506 |
render: function (data, type, row, meta) { |
| 2507 |
return escape_str(row.callnumber || ''); |
| 2508 |
} |
| 2509 |
}, |
| 2510 |
{ |
| 2511 |
data: "", |
| 2512 |
searchable: false, |
| 2513 |
orderable: false, |
| 2514 |
render: function (data, type, row, meta) { |
| 2515 |
let status = ''; |
| 2516 |
if (row._status && row._status.length > 0) { |
| 2517 |
status = row._status.map(s => s.replace(/_/g, ' ')).join(', '); |
| 2518 |
} else { |
| 2519 |
status = 'Available'; |
| 2520 |
} |
| 2521 |
return escape_str(status); |
| 2522 |
} |
| 2523 |
} |
| 2524 |
], |
| 2525 |
[% IF Koha.Preference('UseDisplayModule') %] |
| 2526 |
rowCallback: function(row, data) { |
| 2527 |
// Hide items that are already on displays (effective_location starts with "DISPLAY:") |
| 2528 |
if (data.effective_location && data.effective_location.startsWith && data.effective_location.startsWith('DISPLAY:')) { |
| 2529 |
$(row).hide(); |
| 2530 |
return; |
| 2531 |
} |
| 2532 |
$(row).show(); |
| 2533 |
}, |
| 2534 |
[% END %] |
| 2535 |
drawCallback: function(settings) { |
| 2536 |
// Handle individual checkbox changes |
| 2537 |
$('.display-item-checkbox').off('change').on('change', function() { |
| 2538 |
const itemId = parseInt($(this).val()); |
| 2539 |
if ($(this).is(':checked')) { |
| 2540 |
if (!selectedDisplayItems.includes(itemId)) { |
| 2541 |
selectedDisplayItems.push(itemId); |
| 2542 |
} |
| 2543 |
} else { |
| 2544 |
selectedDisplayItems = selectedDisplayItems.filter(id => id !== itemId); |
| 2545 |
} |
| 2546 |
updateSelectAllState(); |
| 2547 |
}); |
| 2548 |
|
| 2549 |
// Handle select all checkbox |
| 2550 |
$('#select-all-display-items').off('change').on('change', function() { |
| 2551 |
const isChecked = $(this).is(':checked'); |
| 2552 |
$('.display-item-checkbox').prop('checked', isChecked); |
| 2553 |
|
| 2554 |
if (isChecked) { |
| 2555 |
selectedDisplayItems = []; |
| 2556 |
$('.display-item-checkbox').each(function() { |
| 2557 |
selectedDisplayItems.push(parseInt($(this).val())); |
| 2558 |
}); |
| 2559 |
} else { |
| 2560 |
selectedDisplayItems = []; |
| 2561 |
} |
| 2562 |
}); |
| 2563 |
} |
| 2564 |
}); |
| 2565 |
} |
| 2566 |
|
| 2567 |
function updateSelectAllState() { |
| 2568 |
const totalCheckboxes = $('.display-item-checkbox').length; |
| 2569 |
const checkedCheckboxes = $('.display-item-checkbox:checked').length; |
| 2570 |
|
| 2571 |
if (checkedCheckboxes === 0) { |
| 2572 |
$('#select-all-display-items').prop('indeterminate', false).prop('checked', false); |
| 2573 |
} else if (checkedCheckboxes === totalCheckboxes) { |
| 2574 |
$('#select-all-display-items').prop('indeterminate', false).prop('checked', true); |
| 2575 |
} else { |
| 2576 |
$('#select-all-display-items').prop('indeterminate', true); |
| 2577 |
} |
| 2578 |
} |
| 2579 |
|
| 2580 |
function openDisplayModal(displayId = null) { |
| 2581 |
if (displayId) { |
| 2582 |
$('#display-select').val(displayId); |
| 2583 |
} |
| 2584 |
// Initialize the DataTable when the modal is opened |
| 2585 |
initializeDisplayItemsTable(); |
| 2586 |
$('#addToDisplayModal').modal('show'); |
| 2587 |
} |
| 2588 |
|
| 2589 |
// Clear date button functionality |
| 2590 |
$('#clear-date').on('click', function() { |
| 2591 |
$('#date-remove').val(''); |
| 2592 |
}); |
| 2593 |
|
| 2594 |
// Form submission |
| 2595 |
$('#addToDisplayForm').on('submit', function(e) { |
| 2596 |
e.preventDefault(); |
| 2597 |
|
| 2598 |
const displayId = $('#display-select').val(); |
| 2599 |
const removeDate = $('#date-remove').val(); |
| 2600 |
|
| 2601 |
if (!displayId) { |
| 2602 |
alert('Please select a display'); |
| 2603 |
return; |
| 2604 |
} |
| 2605 |
|
| 2606 |
if (selectedDisplayItems.length === 0) { |
| 2607 |
alert('Please select at least one item'); |
| 2608 |
return; |
| 2609 |
} |
| 2610 |
|
| 2611 |
$('#add-to-display-btn').prop('disabled', true).text('Adding...'); |
| 2612 |
|
| 2613 |
// Add each item to the display |
| 2614 |
let promises = selectedDisplayItems.map(itemId => { |
| 2615 |
const data = { |
| 2616 |
display_id: parseInt(displayId), |
| 2617 |
itemnumber: parseInt(itemId), |
| 2618 |
biblionumber: [% biblionumber | html %], |
| 2619 |
date_added: new Date().toISOString().split('T')[0] |
| 2620 |
}; |
| 2621 |
if (removeDate) { |
| 2622 |
data.date_remove = removeDate; |
| 2623 |
} |
| 2624 |
|
| 2625 |
return $.ajax({ |
| 2626 |
url: '/api/v1/display/items', |
| 2627 |
method: 'POST', |
| 2628 |
contentType: 'application/json', |
| 2629 |
data: JSON.stringify(data) |
| 2630 |
}); |
| 2631 |
}); |
| 2632 |
|
| 2633 |
Promise.all(promises).then(() => { |
| 2634 |
alert(`Successfully added ${selectedDisplayItems.length} item(s) to display`); |
| 2635 |
$('#addToDisplayModal').modal('hide'); |
| 2636 |
$('#addToDisplayForm')[0].reset(); |
| 2637 |
selectedDisplayItems = []; |
| 2638 |
|
| 2639 |
// Safely refresh modal table |
| 2640 |
try { |
| 2641 |
if (displayItemsTable && displayItemsTable.DataTable) { |
| 2642 |
displayItemsTable.DataTable().draw(); |
| 2643 |
} |
| 2644 |
} catch (e) { |
| 2645 |
// Modal table refresh failed - not critical |
| 2646 |
} |
| 2647 |
|
| 2648 |
// Safely refresh the main items table to show updated locations |
| 2649 |
try { |
| 2650 |
if (typeof build_items_table === 'function') { |
| 2651 |
$('.items_table').each(function() { |
| 2652 |
let table_id = $(this).attr('id').replace('_table', ''); |
| 2653 |
build_items_table(table_id, false, { destroy: true }); |
| 2654 |
}); |
| 2655 |
} |
| 2656 |
} catch (e) { |
| 2657 |
// Main table refresh failed - reload page as fallback |
| 2658 |
window.location.reload(); |
| 2659 |
} |
| 2660 |
}).catch((error) => { |
| 2661 |
console.error('Error adding items to display:', error); |
| 2662 |
alert('Failed to add some items to display. Please try again.'); |
| 2663 |
}).finally(() => { |
| 2664 |
$('#add-to-display-btn').prop('disabled', false).text('Add to display'); |
| 2665 |
}); |
| 2666 |
}); |
| 2667 |
|
| 2668 |
// Clean up when modal is hidden |
| 2669 |
$('#addToDisplayModal').on('hidden.bs.modal', function() { |
| 2670 |
if (displayItemsTable && displayItemsTable.DataTable) { |
| 2671 |
displayItemsTable.DataTable().destroy(); |
| 2672 |
displayItemsTable = null; |
| 2673 |
} |
| 2674 |
selectedDisplayItems = []; |
| 2675 |
$('#addToDisplayForm')[0].reset(); |
| 2676 |
}); |
| 2677 |
[% END %] |
| 2371 |
</script> |
2678 |
</script> |
| 2372 |
[% CoverImagePlugins | $raw %] |
2679 |
[% CoverImagePlugins | $raw %] |
| 2373 |
[% END # /jsinclude %] |
2680 |
[% END # /jsinclude %] |
|
|
2681 |
|
| 2682 |
[% BLOCK cssinclude %] |
| 2683 |
<style> |
| 2684 |
.display-location { |
| 2685 |
font-weight: bold; |
| 2686 |
color: #d9534f; |
| 2687 |
} |
| 2688 |
.display-location::before { |
| 2689 |
content: "📺 "; |
| 2690 |
font-size: 0.8em; |
| 2691 |
} |
| 2692 |
.original-location { |
| 2693 |
font-size: 0.9em; |
| 2694 |
font-style: italic; |
| 2695 |
} |
| 2696 |
</style> |
| 2697 |
[% END %] |
| 2374 |
[% INCLUDE 'intranet-bottom.inc' %] |
2698 |
[% INCLUDE 'intranet-bottom.inc' %] |