Files
Berufsschule_HAM/src/Views/Shared/_Batch.cshtml

560 lines
23 KiB
Plaintext

@using Berufsschule_HAM.Services
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer T
@inject IConfiguration Configuration
@inject LdapService ldap
@{
AdminSettingsModel adminSettingsModel = await ldap.GetAdminSettingsModelAsync();
string barcodeType = adminSettingsModel.BarcodeType;
string barcodeText = adminSettingsModel.BarcodeText;
}
<!-- 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">
<h3 class="modal-title" id="printModalLabel">@T["Print Page"]</h3>
<button type="button" class="btn-close btn-close-white" aria-label="Close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
<script>
const BARCODE_TYPE = '@barcodeType';
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>@T["No assets selected for printing."]</p>';
return;
}
modalBody.innerHTML = `
<div class="row g-3">
<h4 class="fw-bold">@T["Print"]</h4>
<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">
<h4 class="fw-bold">@T["Layout"]</h4>
<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}">
<h4 class="card-title mb-4" style="text-align: center;"><button class="btn btn-sm btn-danger" data-action="remove" data-batchid="${i}" style="float:left;">X</button><strong>Asset ${i + 1}:</strong> ${asset.Name}</h4>
<div class="row">
<div class="col-md-5">
<p><strong>@T["Asset ID"]:</strong> ${asset.Cn}</p>
<p><strong>@T["Owner"]:</strong> ${asset.Owner}</p>
<p><strong>@T["Type"]:</strong> ${asset.Description.Type}</p>
</div>
<div class="col-md-5">
<p><strong>@T["Make"]:</strong> ${asset.Description.Make}</p>
<p><strong>@T["Model"]:</strong> ${asset.Description.Model}</p>
<p><strong>@T["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">
<h4 class="card-title mb-4" style="text-align: center;"><button class="btn btn-sm btn-danger" data-action="remove" data-batchid="${i}" style="float:left;">X</button><strong>Asset ${i + 1}:</strong> @T["Empty"]</h4>
<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 = '↓';
let removeButton = document.querySelector(`button.btn-danger[data-batchid="${i}"]`);
container.appendChild(upButton);
container.appendChild(document.createElement('br'));
container.appendChild(downButton);
container.appendChild(document.createElement('br'));
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");
});
removeButton.addEventListener('click', async function() {
const index = parseInt(removeButton.closest('[data-card-index]').getAttribute('data-card-index'));
let batch = getAssetIdsFromBatch();
if (batch && batch.length > index) {
batch.splice(index, 1);
localStorage.setItem("printBatch", JSON.stringify(batch));
}
await renderBarcodePreview("printPreviewContainer");
removeButton.closest('.card').remove();
document.querySelectorAll('#printModal .card').forEach((card, newIndex) => {
card.setAttribute('data-card-index', newIndex);
const title = card.querySelector('.card-title strong');
if (title) title.textContent = `Asset ${newIndex + 1}:`;
});
});
} catch (error) {
console.error(`Error fetching asset ${assetId}:`, error);
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = `@T["Error loading asset"] ${assetId}`;
modalBody.appendChild(errorDiv);
}
}
enableDragAndDrop();
});
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">@T["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";
cell.setAttribute("data-id", i);
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">@T["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, getBarcodeValue(BARCODE_TYPE, asset.Cn), {
format: BARCODE_TYPE,
lineColor: "#000",
width: isMobileM ? 1 : (isMobileS ? 0.5 : 2),
height: isMobileM ? 20 : (isMobileS ? 20 : 40),
displayValue: true,
fontSize: 12,
});
} catch (error) {
console.error(`@T["Error rendering asset"] ${assetId}:`, error);
cell.innerHTML = `<div class="text-danger small">@T["Error loading"] ${assetId}</div>`;
grid.appendChild(cell);
}
}
enableBarcodeDragAndDrop();
}
function moveCardInBatch(index, direction) {
const newIndex = direction === 'up' ? index - 1 : index - 0 + 1;
swapCardsInBatch(index, newIndex);
}
function swapCardsInBatch(index, newIndex) {
let batch = getAssetIdsFromBatch();
padNulls(batch, 24);
if (!batch || batch.length === 0) return;
// 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[1].textContent = `Asset ${newIndex + 1}`;
let cardAtTarget = document.querySelector(`[data-card-index="${newIndex}"]`)
let cardBodyAtTarget = cardAtTarget.children[0];
cardBodyAtTarget.children[0].children[1].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 enableDragAndDrop() {
const cards = document.querySelectorAll('#printModal .card');
const modalBody = document.querySelector('#printModal .modal-body');
let draggedCard = null;
cards.forEach(card => {
card.setAttribute('draggable', 'true');
card.addEventListener('dragstart', e => {
draggedCard = card;
e.dataTransfer.effectAllowed = 'move';
e.target.style.opacity = '0.5';
});
card.addEventListener('dragend', e => {
e.target.style.opacity = '1';
draggedCard = null;
updateBatchOrderFromDOM();
});
card.addEventListener('dragover', e => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const bounding = card.getBoundingClientRect();
const offset = bounding.y + (bounding.height / 2);
if (e.clientY - offset > 0) {
card.style['border-bottom'] = '2px solid #007bff';
card.style['border-top'] = '';
} else {
card.style['border-top'] = '2px solid #007bff';
card.style['border-bottom'] = '';
}
});
card.addEventListener('dragleave', e => {
card.style['border-bottom'] = '';
card.style['border-top'] = '';
});
card.addEventListener('drop', e => {
e.preventDefault();
card.style['border-bottom'] = '';
card.style['border-top'] = '';
const bounding = card.getBoundingClientRect();
const offset = bounding.y + (bounding.height / 2);
if (e.clientY - offset > 0) {
card.after(draggedCard);
} else {
card.before(draggedCard);
}
updateBatchOrderFromDOM();
});
});
}
function updateBatchOrderFromDOM() {
const newOrder = [];
const cards = document.querySelectorAll('#printModal .card');
cards.forEach((card, index) => {
// Update index attribute
card.setAttribute('data-card-index', index);
// Update the title text
const title = card.querySelector('.card-title strong');
if (title) title.textContent = `Asset ${index + 1}:`;
// Get asset CN if available
const assetCn = card.querySelector('.card-body')?.getAttribute('data-cn');
newOrder.push(assetCn || null);
});
localStorage.setItem("printBatch", JSON.stringify(newOrder));
renderBarcodePreview("printPreviewContainer");
}
function enableBarcodeDragAndDrop() {
const cells = document.querySelectorAll('#printPreviewContainer .barcode-cell');
const container = document.querySelector('#printPreviewContainer .barcode-grid');
let draggedCell = null;
cells.forEach(cell => {
cell.setAttribute('draggable', 'true');
cell.addEventListener('dragstart', e => {
draggedCell = cell;
e.dataTransfer.effectAllowed = 'move';
cell.style.opacity = '0.5';
});
cell.addEventListener('dragend', e => {
cell.style.opacity = '1';
draggedCell = null;
});
cell.addEventListener('dragover', e => {
e.preventDefault();
const bounding = cell.getBoundingClientRect();
const offset = bounding.y + bounding.height / 2;
if (e.clientY - offset > 0) {
cell.style['border-bottom'] = '2px solid #007bff';
cell.style['border-top'] = '';
} else {
cell.style['border-top'] = '2px solid #007bff';
cell.style['border-bottom'] = '';
}
});
cell.addEventListener('dragleave', e => {
cell.style['border-bottom'] = '';
cell.style['border-top'] = '';
});
cell.addEventListener('drop', e => {
e.preventDefault();
cell.style['border-bottom'] = '';
cell.style['border-top'] = '';
cell.after(draggedCell);
let index = cell.getAttribute("data-id");
let newIndex = draggedCell.getAttribute("data-id");
swapCardsInBatch(index, newIndex);
});
});
}
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("@T["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 id="jsbarcodeScript" src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"><\/script>
</body>
</html>
`);
const waitForJsBarcode = new Promise(resolve => {
const script = printWindow.document.getElementById("jsbarcodeScript");
script.onload = () => resolve();
});
await waitForJsBarcode;
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 = "@Html.Raw(barcodeText)";
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}`, getBarcodeValue(BARCODE_TYPE, asset.Cn), {
format: BARCODE_TYPE,
lineColor: "#000",
width: 2,
height: 50,
displayValue: true,
fontSize: 12,
});
} catch (err) {
console.error(err);
const errDiv = printWindow.document.createElement("div");
errDiv.className = "barcode-label text-danger";
errDiv.textContent = `@T["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>