Initial commit
This commit is contained in:
@@ -0,0 +1,677 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CSG First-Time Setup: Batch Label Generator</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #0d0f12;
|
||||
--panel-bg: rgba(22, 26, 33, 0.85);
|
||||
--border-color: rgba(255, 255, 255, 0.08);
|
||||
--accent-color: #3b82f6;
|
||||
--accent-hover: #2563eb;
|
||||
--text-primary: #f3f4f6;
|
||||
--text-secondary: #9ca3af;
|
||||
--success-color: #10b981;
|
||||
--error-color: #ef4444;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
background: rgba(13, 15, 18, 0.7);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 1rem 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.logo-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: var(--accent-color);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 12px var(--accent-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 0.8rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-color: var(--text-secondary);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 320px;
|
||||
background: var(--panel-bg);
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.form-group input, .form-group select {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.form-group input:focus, .form-group select:focus {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
button.primary-btn {
|
||||
background: var(--accent-color);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0.6rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
button.primary-btn:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
button.secondary-btn {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 0.6rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
button.secondary-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.status-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Labels View grid default screen preview */
|
||||
.preview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Label style in preview */
|
||||
.label-card {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #ddd;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.label-qr-wrap {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.label-text-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0; /* allows text truncation/wrap */
|
||||
}
|
||||
|
||||
.label-sku {
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
word-break: break-all;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.label-meta {
|
||||
font-size: 11px;
|
||||
color: #555;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.progress-bar-wrap {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 9999px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: var(--accent-color);
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
/* Printing Specific Styles */
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body, html {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
height: auto !important;
|
||||
min-height: auto !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
main {
|
||||
display: block !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
display: block !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Printing layout based on selection */
|
||||
.preview-grid.print-cols-1 {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.preview-grid.print-cols-2 {
|
||||
display: grid !important;
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
|
||||
.preview-grid.print-cols-3 {
|
||||
display: grid !important;
|
||||
grid-template-columns: 1fr 1fr 1fr !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.label-card {
|
||||
box-shadow: none !important;
|
||||
border: 1px solid #aaa !important;
|
||||
border-radius: 4px !important;
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
margin: 0 !important;
|
||||
page-break-inside: avoid !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Header (Hidden during print) -->
|
||||
<header class="no-print">
|
||||
<div class="header-brand">
|
||||
<div class="logo-dot"></div>
|
||||
<div>
|
||||
<h1>CSG Warehouse Setup</h1>
|
||||
<p>Batch Label Generation & Printing Utility</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/picker" class="back-btn">⬅ Back to Picker Home</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Sidebar Controls (Hidden during print) -->
|
||||
<div class="sidebar no-print">
|
||||
<h2>🔍 Filter Parts</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="filter-sku">SKU Search / Prefix</label>
|
||||
<input type="text" id="filter-sku" placeholder="e.g. 84126" oninput="applyFilters()">
|
||||
</div>
|
||||
|
||||
<h2>📏 Label Layout</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="print-cols">Layout Columns (for print)</label>
|
||||
<select id="print-cols" onchange="updatePrintCols()">
|
||||
<option value="print-cols-1">Roll Printer (1 Column)</option>
|
||||
<option value="print-cols-2" selected>Sheet Paper (2 Columns)</option>
|
||||
<option value="print-cols-3">Sheet Paper (3 Columns)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="qr-mode">QR Code Mode</label>
|
||||
<select id="qr-mode" onchange="applyFilters()">
|
||||
<option value="sku">SKU only (highly recommended)</option>
|
||||
<option value="sku_bin">SKU + Bin Location</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="batch-limit">Batch Render Limit</label>
|
||||
<select id="batch-limit" onchange="applyFilters()">
|
||||
<option value="50">First 50 matches</option>
|
||||
<option value="100" selected>First 100 matches</option>
|
||||
<option value="250">First 250 matches</option>
|
||||
<option value="500">First 500 matches</option>
|
||||
<option value="all">⚠️ Render All (2,000+ parts)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h2>💾 Import / Export</h2>
|
||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 0.5rem;">
|
||||
<button class="secondary-btn" style="flex: 1; padding: 0.5rem;" onclick="triggerUpload()">📤 Upload</button>
|
||||
<button class="secondary-btn" style="flex: 1; padding: 0.5rem;" onclick="exportLabels()">⬇️ Export</button>
|
||||
</div>
|
||||
<input type="file" id="upload-file-input" accept=".json,.csv" style="display:none;" onchange="handleUpload(event)">
|
||||
|
||||
<button class="primary-btn" onclick="triggerPrint()">🖨️ Print Labels</button>
|
||||
<button class="secondary-btn" onclick="resetFilters()">🔄 Reset Filters</button>
|
||||
</div>
|
||||
|
||||
<!-- Printable content / preview area -->
|
||||
<div class="content-area">
|
||||
|
||||
<!-- Setup Progress / Status Banner (Hidden during print) -->
|
||||
<div class="status-card no-print">
|
||||
<div class="status-info">
|
||||
<span class="status-title" id="status-title">Loading parts inventory...</span>
|
||||
<span class="status-desc" id="status-desc">Fetching latest warehouse spreadsheet database.</span>
|
||||
<div class="progress-bar-wrap" id="progress-wrap">
|
||||
<div class="progress-bar-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Label Grid -->
|
||||
<div class="preview-grid print-cols-2" id="labels-grid">
|
||||
<!-- Dynamically loaded label cards -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
|
||||
|
||||
<script>
|
||||
let allParts = [];
|
||||
let filteredParts = [];
|
||||
let renderQueue = [];
|
||||
let isRendering = false;
|
||||
|
||||
// Fetch all parts on load
|
||||
async function fetchParts() {
|
||||
try {
|
||||
const response = await fetch('/api/parts');
|
||||
if (!response.ok) throw new Error('Failed to load parts api');
|
||||
const data = await response.json();
|
||||
allParts = data.parts || [];
|
||||
|
||||
document.getElementById('status-title').textContent = `Loaded ${allParts.length} parts database.`;
|
||||
document.getElementById('status-desc').textContent = 'Filter below to print barcodes in batches.';
|
||||
|
||||
applyFilters();
|
||||
} catch (err) {
|
||||
document.getElementById('status-title').textContent = '⚠️ Inventory Database Error';
|
||||
document.getElementById('status-desc').textContent = 'Failed to fetch parts. Ensure picker server is running.';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function triggerUpload() {
|
||||
document.getElementById('upload-file-input').click();
|
||||
}
|
||||
|
||||
function handleUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
const text = e.target.result;
|
||||
let parsedList = [];
|
||||
|
||||
try {
|
||||
if (file.name.endsWith('.json')) {
|
||||
// Parse JSON
|
||||
const data = JSON.parse(text);
|
||||
const items = Array.isArray(data) ? data : (data.parts || data.items || []);
|
||||
parsedList = items.map(item => ({
|
||||
sku: (item.sku || item.SKU || item.stock_number || item['STOCK NUMBER'] || '').trim().toUpperCase(),
|
||||
description: (item.description || item.DESCRIPTION || item.desc || '')
|
||||
})).filter(x => x.sku);
|
||||
} else {
|
||||
// Parse CSV
|
||||
const lines = text.split(/\r?\n/);
|
||||
if (lines.length > 0) {
|
||||
const headers = lines[0].split(',').map(h => h.trim().toUpperCase());
|
||||
|
||||
// Find column indices
|
||||
let skuIdx = headers.findIndex(h => h === 'SKU' || h === 'STOCK NUMBER' || h === 'STOCK_NUMBER');
|
||||
let descIdx = headers.findIndex(h => h === 'DESCRIPTION' || h === 'DESC');
|
||||
|
||||
// Fallback if no header match
|
||||
if (skuIdx === -1) skuIdx = 0;
|
||||
if (descIdx === -1) descIdx = 1;
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
|
||||
// Split comma-separated, respecting quoted fields
|
||||
const cols = line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/).map(c => c.replace(/^"|"$/g, '').trim());
|
||||
const sku = (cols[skuIdx] || '').toUpperCase();
|
||||
const description = cols[descIdx] || '';
|
||||
|
||||
if (sku) {
|
||||
parsedList.push({ sku, description });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedList.length === 0) {
|
||||
alert("No valid part labels found in the uploaded file. Ensure columns contain SKU/STOCK NUMBER and DESCRIPTION.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Update parts list
|
||||
allParts = parsedList;
|
||||
document.getElementById('status-title').textContent = `Uploaded Custom List: ${allParts.length} parts loaded.`;
|
||||
document.getElementById('status-desc').textContent = 'Successfully imported custom label batch.';
|
||||
|
||||
// Reset search input
|
||||
document.getElementById('filter-sku').value = '';
|
||||
applyFilters();
|
||||
|
||||
alert(`Successfully imported ${allParts.length} custom labels!`);
|
||||
} catch (err) {
|
||||
alert("Error parsing file: " + err.message);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
function exportLabels() {
|
||||
if (filteredParts.length === 0) {
|
||||
alert("No labels to export.");
|
||||
return;
|
||||
}
|
||||
|
||||
const exportData = filteredParts.map(part => ({
|
||||
sku: part.sku,
|
||||
description: part.description
|
||||
}));
|
||||
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportData, null, 2));
|
||||
const dlAnchor = document.createElement('a');
|
||||
dlAnchor.setAttribute("href", dataStr);
|
||||
dlAnchor.setAttribute("download", `CSG_Labels_Export_${Date.now()}.json`);
|
||||
document.body.appendChild(dlAnchor);
|
||||
dlAnchor.click();
|
||||
dlAnchor.remove();
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
const skuVal = document.getElementById('filter-sku').value.toUpperCase().trim();
|
||||
const limitVal = document.getElementById('batch-limit').value;
|
||||
|
||||
// Apply filters
|
||||
filteredParts = allParts.filter(part => {
|
||||
if (skuVal && !part.sku.includes(skuVal)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Handle limit
|
||||
const limit = limitVal === 'all' ? filteredParts.length : parseInt(limitVal, 10);
|
||||
renderQueue = filteredParts.slice(0, limit);
|
||||
|
||||
// Re-render
|
||||
startBatchRender();
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
document.getElementById('filter-sku').value = '';
|
||||
document.getElementById('batch-limit').value = '100';
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function updatePrintCols() {
|
||||
const grid = document.getElementById('labels-grid');
|
||||
grid.className = 'preview-grid ' + document.getElementById('print-cols').value;
|
||||
}
|
||||
|
||||
// Start progressive asynchronous rendering of QRs to keep page responsive
|
||||
function startBatchRender() {
|
||||
isRendering = false; // stops active loops
|
||||
const grid = document.getElementById('labels-grid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
if (renderQueue.length === 0) {
|
||||
grid.innerHTML = '<div style="color:var(--text-secondary); grid-column: 1 / -1; text-align: center; padding: 2rem;">No matching parts found.</div>';
|
||||
document.getElementById('status-title').textContent = 'Loaded 0 labels';
|
||||
document.getElementById('progress-wrap').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('status-title').textContent = `Generating ${renderQueue.length} labels...`;
|
||||
document.getElementById('progress-wrap').style.display = 'block';
|
||||
document.getElementById('progress-fill').style.width = '0%';
|
||||
|
||||
isRendering = true;
|
||||
let index = 0;
|
||||
|
||||
const qrMode = document.getElementById('qr-mode').value;
|
||||
|
||||
function renderNextChunk() {
|
||||
if (!isRendering) return;
|
||||
|
||||
const chunk = 10; // Render 10 at a time
|
||||
const end = Math.min(index + chunk, renderQueue.length);
|
||||
|
||||
for (let i = index; i < end; i++) {
|
||||
const part = renderQueue[i];
|
||||
createLabelCard(grid, part, i, qrMode);
|
||||
}
|
||||
|
||||
index = end;
|
||||
const percentage = (index / renderQueue.length) * 100;
|
||||
document.getElementById('progress-fill').style.width = percentage + '%';
|
||||
|
||||
if (index < renderQueue.length) {
|
||||
requestAnimationFrame(renderNextChunk);
|
||||
} else {
|
||||
document.getElementById('status-title').textContent = `Ready: ${renderQueue.length} labels generated.`;
|
||||
document.getElementById('status-desc').textContent = 'Press "Print Labels" to open printer queue dialog.';
|
||||
document.getElementById('progress-wrap').style.display = 'none';
|
||||
isRendering = false;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(renderNextChunk);
|
||||
}
|
||||
|
||||
function createLabelCard(container, part, index, qrMode) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'label-card';
|
||||
|
||||
const qrWrap = document.createElement('div');
|
||||
qrWrap.className = 'label-qr-wrap';
|
||||
qrWrap.id = `setup-qr-wrap-${index}`;
|
||||
|
||||
const textWrap = document.createElement('div');
|
||||
textWrap.className = 'label-text-wrap';
|
||||
|
||||
const skuDiv = document.createElement('div');
|
||||
skuDiv.className = 'label-sku';
|
||||
skuDiv.textContent = part.sku;
|
||||
|
||||
const metaDiv = document.createElement('div');
|
||||
metaDiv.className = 'label-meta';
|
||||
metaDiv.innerHTML = part.description || 'No description available';
|
||||
|
||||
textWrap.appendChild(skuDiv);
|
||||
textWrap.appendChild(metaDiv);
|
||||
card.appendChild(qrWrap);
|
||||
card.appendChild(textWrap);
|
||||
container.appendChild(card);
|
||||
|
||||
// Determine QR code text
|
||||
let qrText = part.sku;
|
||||
if (qrMode === 'sku_bin' && part.bin_location) {
|
||||
qrText = `${part.sku}\n${part.bin_location}`;
|
||||
}
|
||||
|
||||
// Create QR code
|
||||
new QRCode(qrWrap, {
|
||||
text: qrText,
|
||||
width: 80,
|
||||
height: 80,
|
||||
colorDark: "#000000",
|
||||
colorLight: "#ffffff",
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
});
|
||||
}
|
||||
|
||||
function triggerPrint() {
|
||||
if (isRendering) {
|
||||
alert("Please wait for all labels to finish rendering before printing!");
|
||||
return;
|
||||
}
|
||||
window.print();
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
window.addEventListener('DOMContentLoaded', fetchParts);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user