Added batch printing to Assets, Moved batch printing to partial view

This commit is contained in:
2025-10-18 17:37:55 +02:00
parent 7193e2b78b
commit f64e360796
4 changed files with 416 additions and 402 deletions

View File

@@ -0,0 +1,394 @@
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer T
<!-- Print Modal -->
<div class="modal fade" id="printModal" tabindex="-1" aria-labelledby="printModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="printModalLabel">Print Page</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
<script>
const printModal = document.getElementById('printModal');
printModal.addEventListener('show.bs.modal', async () => {
var assetIds = getAssetIdsFromBatch();
padNulls(assetIds, 24);
const modalBody = document.querySelector('#printModal .modal-body');
modalBody.innerHTML = '';
if (!assetIds || assetIds.length === 0) {
modalBody.innerHTML = '<p>No assets selected for printing.</p>';
return;
}
modalBody.innerHTML = `
<div class="row g-3">
<h6 class="fw-bold">@T["Print"]</h6>
<button class="btn btn-primary" onclick="printBatch();" type="button">
@T["Print batch"]
</button>
<div id="printPreviewContainer">
</div>
</div>
<div class="row g-3 mt-3">
<button class="btn btn-warning" onclick="clearBatch();" type="button" style="width: auto; margin: auto;">
@T["Clear batch"]
</button>
</div>
<hr class="my-3">
<h6 class="fw-bold">@T["Layout"]</h6>
<br/>
`;
await renderBarcodePreview("printPreviewContainer");
for (let i = 0; i < assetIds.length; i++) {
const assetId = assetIds[i];
try {
const assetCard = document.createElement('div');
assetCard.className = 'card mb-3';
assetCard.setAttribute("data-card-index", i);
if (assetId) {
const response = await fetch(`/Assets/Get?cn=${assetId}`);
const json = await response.json();
const asset = json.assetsModel;
assetCard.innerHTML = `
<div class="card-body" data-cn="${asset.Cn}">
<h6 class="card-title"><strong>Asset ${i + 1}:</strong> ${asset.Name}</h6>
<div class="row">
<div class="col-md-5">
<p><strong>Asset ID:</strong> ${asset.Cn}</p>
<p><strong>Owner:</strong> ${asset.Owner}</p>
<p><strong>Type:</strong> ${asset.Description.Type}</p>
</div>
<div class="col-md-5">
<p><strong>Make:</strong> ${asset.Description.Make}</p>
<p><strong>Model:</strong> ${asset.Description.Model}</p>
<p><strong>Serial:</strong> ${asset.SerialNumber}</p>
</div>
<div id="printPreviewBatchButtons${i}" class="col-md-1 justify-content-end" style="margin-left: auto; width: auto;">
</div>
</div>
</div>
`;
} else {
assetCard.innerHTML = `
<div class="card-body">
<h6 class="card-title"><strong>Asset ${i}:</strong> @T["Empty"]</h6>
<div class="row">
<div id="printPreviewBatchButtons${i}" class="col-md-1 justify-content-end" style="margin-left: auto; width: auto;">
</div>
</div>
</div>
`;
}
modalBody.appendChild(assetCard);
const container = document.getElementById(`printPreviewBatchButtons${i}`);
container.innerHTML = '';
let upButton = document.createElement("button");
upButton.setAttribute('data-action', 'up');
upButton.setAttribute('data-batchId', i);
upButton.className = 'btn btn-sm btn-primary mb-1';
upButton.textContent = '↑';
let downButton = document.createElement('button');
downButton.setAttribute('data-action', 'down');
downButton.setAttribute('data-batchId', i);
downButton.className = 'btn btn-sm btn-primary';
downButton.textContent = '↓';
container.appendChild(upButton);
container.appendChild(document.createElement('br'));
container.appendChild(downButton);
upButton.addEventListener('click', function() {
moveCardInBatch(upButton.parentElement.parentElement.parentElement.parentElement.attributes["data-card-index"].value, "up");
});
downButton.addEventListener('click', function() {
moveCardInBatch(downButton.parentElement.parentElement.parentElement.parentElement.attributes["data-card-index"].value, "down");
});
} catch (error) {
console.error(`Error fetching asset ${assetId}:`, error);
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = `Error loading asset ${assetId}`;
modalBody.appendChild(errorDiv);
}
}
});
async function renderBarcodePreview(containerId = "printPreviewContainer") {
const container = document.getElementById(containerId);
if (!container) return console.error(`Container #${containerId} not found.`);
container.innerHTML = `<p class="text-muted text-center">Loading preview...</p>`;
let batch = JSON.parse(localStorage.getItem("printBatch")) || [];
padNulls(batch, 24);
container.innerHTML = "";
// Create the grid
const grid = document.createElement("div");
grid.className = "barcode-grid d-grid gap-3";
grid.style.gridTemplateColumns = "repeat(3, 1fr)";
grid.style.gridTemplateRows = "repeat(8, auto)";
grid.style.textAlign = "center";
container.appendChild(grid);
for (let i = 0; i < batch.length; i++) {
const cell = document.createElement("div");
cell.className = "barcode-cell border rounded p-1 d-flex flex-column align-items-center justify-content-center";
cell.style.minHeight = "120px";
var isMobileM = window.innerWidth < 992 && window.innerWidth >= 576;
var isMobileS = window.innerWidth < 576;
const assetId = batch[i];
if (!assetId) {
cell.innerHTML = `<div class="text-muted small">Empty Slot</div>`;
grid.appendChild(cell);
continue;
}
try {
const response = await fetch(`/Assets/Get?cn=${assetId}`);
const json = await response.json();
const asset = json.assetsModel;
const label = document.createElement("div");
label.className = "small fw-bold";
label.textContent = asset?.Name || assetId;
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.id = `barcodePreview_${i}`;
svg.style.width = "100%";
svg.style.height = "60px";
cell.appendChild(label);
cell.appendChild(svg);
grid.appendChild(cell);
JsBarcode(svg, idToEAN13(asset.Cn), {
format: "EAN13",
lineColor: "#000",
width: isMobileM ? 1 : (isMobileS ? 0.5 : 2),
height: isMobileM ? 20 : (isMobileS ? 20 : 40),
displayValue: true,
fontSize: 12,
});
} catch (error) {
console.error(`Error rendering asset ${assetId}:`, error);
cell.innerHTML = `<div class="text-danger small">Error loading ${assetId}</div>`;
grid.appendChild(cell);
}
}
}
function moveCardInBatch(index, direction) {
let batch = getAssetIdsFromBatch();
padNulls(batch, 24);
if (!batch || batch.length === 0) return;
const newIndex = direction === 'up' ? index - 1 : index - 0 + 1;
// Prevent out-of-bounds movement
if (newIndex < 0 || newIndex >= batch.length) return;
let cardAtIndex = document.querySelector(`[data-card-index="${index}"]`)
let cardBodyAtIndex = cardAtIndex.children[0];
cardBodyAtIndex.children[0].children[0].textContent = `Asset ${newIndex + 1}`;
let cardAtTarget = document.querySelector(`[data-card-index="${newIndex}"]`)
let cardBodyAtTarget = cardAtTarget.children[0];
cardBodyAtTarget.children[0].children[0].textContent = `Asset ${index - 0 + 1}`;
cardAtIndex.insertBefore(cardBodyAtTarget, null);
cardAtTarget.insertBefore(cardBodyAtIndex, null);
// Swap items
[batch[index], batch[newIndex]] = [batch[newIndex], batch[index]];
// Save new order
localStorage.setItem("printBatch", JSON.stringify(batch));
renderBarcodePreview();
}
function getAssetIdsFromBatch() {
let currentData = JSON.parse(localStorage.getItem("printBatch"));
if (currentData != null)
{
return currentData;
}
return [];
}
function padNulls(array, length) {
while (array.length < length)
{
array.push(null);
}
}
function addAssetIdToBatch(assetId) {
let currentData = JSON.parse(localStorage.getItem("printBatch"));
if (!Array.isArray(currentData)) {
currentData = [];
}
// Find first null index
const nullIndex = currentData.indexOf(null);
if (nullIndex !== -1) {
// Replace first null with assetId
currentData[nullIndex] = assetId;
} else {
// Append if no nulls found
currentData.push(assetId);
}
localStorage.setItem("printBatch", JSON.stringify(currentData));
}
function removeAssetIdFromBatch(uid) {
let content = JSON.parse(localStorage.getItem("printBatch"));
const index = content.indexOf(uid);
if (index > -1) {
content.splice(index, 1);
localStorage.setItem("printBatch", JSON.stringify(content));
}
}
async function printBatch() {
const batch = JSON.parse(localStorage.getItem("printBatch")) || [];
padNulls(batch, 24);
if (!batch.length || batch.every(x => x === null)) {
alert("No assets in print batch.");
return;
}
// Open a new tab for print preview
const printWindow = window.open("", "_blank");
printWindow.document.write(`
<html>
<head>
<title>Print Preview - Barcodes</title>
<style>
@@page {
size: A4;
margin-top: 0.45cm;
margin-bottom: 0.45cm;
margin-left: 0;
margin-right: 0;
}
body {
font-family: Arial, sans-serif;
text-align: center;
margin-top: 0.45cm;
margin-bottom: 0.45cm;
height: 28.8cm;
width: 21cm;
}
.barcode-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(8, auto);
gap: 0mm;
justify-items: center;
align-items: center;
}
.barcode-cell {
border: 1px solid #ccc;
padding: 6mm;
width: 100%;
height: 3.6cm;
box-sizing: border-box;
}
.barcode-label {
font-weight: bold;
font-size: 10pt;
margin-bottom: 4mm;
text-align: center;
word-break: break-word;
}
svg {
width: 100%;
height: 60px;
}
@@media print {
.barcode-cell {
border: none;
}
}
</style>
</head>
<body>
<div id="barcodeGrid" class="barcode-grid"></div>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"><\/script>
</body>
</html>
`);
const grid = printWindow.document.getElementById("barcodeGrid");
for (let i = 0; i < batch.length; i++) {
const assetId = batch[i];
const cell = printWindow.document.createElement("div");
cell.className = "barcode-cell";
if (!assetId) {
cell.innerHTML = `<div class="barcode-label text-muted"></div>`;
grid.appendChild(cell);
continue;
}
try {
const response = await fetch(`/Assets/Get?cn=${assetId}`);
const json = await response.json();
const asset = json.assetsModel;
const label = printWindow.document.createElement("div");
label.className = "barcode-label";
label.textContent = "HAM"; //asset?.Name || assetId;
const svg = printWindow.document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.id = `barcode_${i}`;
cell.appendChild(label);
cell.appendChild(svg);
grid.appendChild(cell);
// Generate barcode
printWindow.JsBarcode(`#barcode_${i}`, idToEAN13(asset.Cn), {
format: "EAN13",
lineColor: "#000",
width: 2,
height: 50,
displayValue: true,
fontSize: 12,
});
svg.viewBox.baseVal.x += 10;
} catch (err) {
console.error(err);
const errDiv = printWindow.document.createElement("div");
errDiv.className = "barcode-label text-danger";
errDiv.textContent = `Error loading ${assetId}`;
cell.appendChild(errDiv);
grid.appendChild(cell);
}
}
printWindow.print();
}
function clearBatch() {
localStorage.setItem("printBatch", JSON.stringify([]));
showToast("@T["Batch cleared successfully"]", "success");
bootstrap.Modal.getInstance('#printModal').hide();
}
</script>