Files
quality_recticel/py_app/app/templates/print_module.html

1503 lines
67 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block head %}
<style>
#label-preview {
background: #fafafa;
position: relative;
overflow: visible;
}
/* Enhanced table styling */
.card.scan-table-card table.print-module-table.scan-table thead th {
border-bottom: 2px solid #dee2e6 !important;
background-color: #f8f9fa !important;
padding: 0.25rem 0.4rem !important;
text-align: left !important;
font-weight: 600 !important;
font-size: 10px !important;
line-height: 1.2 !important;
}
.card.scan-table-card table.print-module-table.scan-table {
width: 100% !important;
border-collapse: collapse !important;
}
.card.scan-table-card table.print-module-table.scan-table tbody tr:hover td {
background-color: #f8f9fa !important;
cursor: pointer !important;
}
.card.scan-table-card table.print-module-table.scan-table tbody tr.selected td {
background-color: #007bff !important;
color: white !important;
}
/* Print Progress Modal Styles */
</style>
{% endblock %}
{% block content %}
<div class="scan-container" style="display: flex; flex-direction: row; gap: 20px; width: 100%; align-items: flex-start;">
<!-- Label Preview Card -->
<div class="card scan-form-card" style="display: flex; flex-direction: column; justify-content: flex-start; align-items: center; min-height: 700px; width: 330px; flex-shrink: 0; position: relative; padding: 15px;">
<div class="label-view-title" style="width: 100%; text-align: center; padding: 0 0 15px 0; font-size: 18px; font-weight: bold; letter-spacing: 0.5px;">Label View</div>
<!-- Pairing Keys Section - Only show dropdown if multiple keys exist -->
<div style="width: 100%; text-align: center; margin-bottom: 15px;">
<!-- Dropdown for multiple keys (hidden by default) -->
<div id="client-select-container" style="display: none; margin-bottom: 8px;">
<label for="client-select" style="font-size: 11px; font-weight: 600; display: block; margin-bottom: 4px;">Select Printer/Client:</label>
<select id="client-select" class="form-control form-control-sm" style="width: 85%; margin: 0 auto; font-size: 11px;"></select>
</div>
<!-- Manage Keys Button -->
<a href="{{ url_for('main.download_extension') }}" class="btn btn-info btn-sm" target="_blank" style="font-size: 11px; padding: 4px 12px;">🔑 Manage Keys</a>
</div>
<!-- Label Preview Section -->
<div id="label-preview" style="border: 1px solid #ddd; padding: 10px; position: relative; background: #fafafa; width: 301px; height: 434.7px;">
<!-- Label content rectangle -->
<div id="label-content" style="position: absolute; top: 65.7px; left: 11.34px; width: 227.4px; height: 321.3px; border: 2px solid #333; background: white;">
<!-- Top row content: Company name -->
<div style="position: absolute; top: 0; left: 0; right: 0; height: 32.13px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px; color: #000; z-index: 10;">
INNOFA ROMANIA SRL
</div>
<!-- Row 2 content: Customer Name -->
<div id="customer-name-row" style="position: absolute; top: 32.13px; left: 0; right: 0; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 11px; color: #000;">
<!-- Customer name will be populated here -->
</div>
<!-- Horizontal dividing lines -->
<div style="position: absolute; top: 32.13px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 64.26px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 96.39px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 128.52px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 160.65px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 224.91px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 257.04px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 289.17px; left: 0; right: 0; height: 1px; background: #999;"></div>
<!-- Vertical dividing line -->
<div style="position: absolute; left: 90.96px; top: 64.26px; width: 1px; height: 257.04px; background: #999;"></div>
<!-- Row 3: Quantity ordered -->
<div style="position: absolute; top: 64.26px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Quantity ordered
</div>
<div id="quantity-ordered-value" style="position: absolute; top: 64.26px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: bold; color: #000;">
<!-- Quantity value will be populated here -->
</div>
<!-- Row 4: Customer order -->
<div style="position: absolute; top: 96.39px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Customer order
</div>
<div id="client-order-info" style="position: absolute; top: 96.39px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; color: #000;">
<!-- Client order info will be populated here -->
</div>
<!-- Row 5: Delivery date -->
<div style="position: absolute; top: 128.52px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Delivery date
</div>
<div id="delivery-date-value" style="position: absolute; top: 128.52px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; color: #000;">
<!-- Delivery date value will be populated here -->
</div>
<!-- Row 6: Description (double height) -->
<div style="position: absolute; top: 160.65px; left: 0; width: 90.96px; height: 64.26px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Product description
</div>
<div id="description-value" style="position: absolute; top: 160.65px; left: 90.96px; width: 136.44px; height: 64.26px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: #000; text-align: center; padding: 2px; overflow: hidden;">
<!-- Description will be populated here -->
</div>
<!-- Row 7: Size -->
<div style="position: absolute; top: 224.91px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Size
</div>
<div id="size-value" style="position: absolute; top: 224.91px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; color: #000;">
<!-- Size value will be populated here -->
</div>
<!-- Row 8: Article Code -->
<div style="position: absolute; top: 257.04px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Article code
</div>
<div id="article-code-value" style="position: absolute; top: 257.04px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: bold; color: #000;">
<!-- Article code will be populated here -->
</div>
<!-- Row 9: Production Order -->
<div style="position: absolute; top: 289.17px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Prod. order
</div>
<div id="prod-order-value" style="position: absolute; top: 289.17px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; color: #000;">
<!-- Production order will be populated here -->
</div>
</div>
<!-- Barcode Frame - positioned 10px below rectangle, centered, 90% of label width -->
<div id="barcode-frame" style="position: absolute; top: 395px; left: 50%; transform: translateX(-50%); width: 90%; max-width: 270px; height: 50px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<!-- Code 128 Barcode representation -->
<svg id="barcode-display" style="width: 100%; height: 40px;"></svg>
<!-- Barcode text below the bars -->
<div id="barcode-text" style="font-size: 8px; font-family: 'Courier New', monospace; margin-top: 2px; text-align: center; font-weight: bold;">
<!-- Barcode text will be populated here -->
</div>
</div>
<!-- Vertical Barcode Frame - positioned on the right side, rotated 90 degrees, spans the height of main rectangle -->
<div id="vertical-barcode-frame" style="position: absolute; top: 50px; left: 270px; width: 321.3px; height: 40px; background: white; display: flex; align-items: center; justify-content: center; transform: rotate(90deg); transform-origin: left center;">
<!-- Vertical Code 128 Barcode representation -->
<svg id="vertical-barcode-display" style="width: 100%; height: 35px;"></svg>
<!-- Vertical barcode text -->
<div id="vertical-barcode-text" style="position: absolute; bottom: -15px; font-size: 7px; font-family: 'Courier New', monospace; text-align: center; font-weight: bold; width: 100%;">
<!-- Vertical barcode text will be populated here -->
</div>
</div>
</div>
<!-- Print Options -->
<div style="width: 100%; margin-top: 20px;">
<!-- Print Method Selection -->
<div style="margin-bottom: 15px;" role="group" aria-labelledby="print-method-label">
<div id="print-method-label" style="font-size: 12px; font-weight: 600; color: #495057; margin-bottom: 8px;">
📄 Print Method:
</div>
<div class="form-check" style="margin-bottom: 6px;">
<input class="form-check-input" type="radio" name="printMethod" id="qzTrayPrint" value="qztray" checked>
<label class="form-check-label" for="qzTrayPrint" style="font-size: 11px; line-height: 1.2;">
<strong>🖨️ Direct Print</strong> <span id="qztray-status" class="badge badge-success" style="font-size: 9px; padding: 2px 6px;">Ready</span>
</label>
</div>
<div class="form-check" id="pdf-option-container" style="display: none; margin-bottom: 6px;">
<input class="form-check-input" type="radio" name="printMethod" id="pdfGenerate" value="pdf">
<label class="form-check-label" for="pdfGenerate" style="font-size: 11px; line-height: 1.2;">
<strong>📄 PDF Export</strong> <span class="text-muted" style="font-size: 10px;">(fallback)</span>
</label>
</div>
</div>
<!-- Printer Selection for QZ Tray (Compact) -->
<div id="qztray-printer-selection" style="margin-bottom: 10px;">
<label for="qztray-printer-select" style="font-size: 11px; font-weight: 600; color: #495057; margin-bottom: 3px; display: block;">
Printer:
</label>
<select id="qztray-printer-select" class="form-control form-control-sm" style="font-size: 11px; padding: 3px 6px;">
<option value="">Loading...</option>
</select>
</div>
<!-- Print Button -->
<div style="width: 100%; text-align: center; margin-bottom: 10px;">
<button id="print-label-btn" class="btn btn-success" style="font-size: 13px; padding: 8px 24px; border-radius: 5px; font-weight: 600;">
<20> Print Labels
</button>
</div>
<!-- Print Information -->
<div style="width: 100%; text-align: center; color: #6c757d; font-size: 10px; line-height: 1.3;">
<small>(e.g., CP00000711-001, 002, ...)</small>
</div>
<!-- QZ Tray Installation Info - Simplified -->
<div id="qztray-info" style="width: 100%; margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef;">
<div style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 10px; text-align: center;">
<div style="font-size: 10px; color: #495057; margin-bottom: 8px;">
QZ Tray is required for direct printing
</div>
<a href="https://filebrowser.moto-adv.com/filebrowser/api/public/dl/Fk0ZaiEY/QP_Tray/qz-tray-2.2.6-SNAPSHOT-x86_64.exe?token=TJ7gSu3CRcWWQuyFLoZv5I8j4diDjP47DDqWRtM0oKAx-2_orj1stfKPJsuuqKR9mE2GQNm1jlZ0BPR7lfZ3gHmu56SkY9fC5AJlC9n_80oX643ojlGc-U7XVb1SDd0w" class="btn btn-outline-secondary btn-sm" style="font-size: 10px; padding: 4px 16px;">
📥 Download QZ Tray
</a>
</div>
</div>
</div>
</div>
<!-- Data Preview Card -->
<div class="card scan-table-card" style="min-height: 700px; width: calc(100% - 350px); margin: 0;">
<h3>Data Preview (Unprinted Orders)</h3>
<button id="check-db-btn" class="btn btn-primary mb-3">Load Orders</button>
<div class="report-table-container">
<table class="scan-table print-module-table">
<thead>
<tr>
<th>ID</th>
<th>Comanda Productie</th>
<th>Cod Articol</th>
<th>Descr. Com. Prod</th>
<th>Cantitate</th>
<th>Data Livrare</th>
<th>Dimensiune</th>
<th>Com. Achiz. Client</th>
<th>Nr. Linie</th>
<th>Customer Name</th>
<th>Customer Art. Nr.</th>
<th>Open Order</th>
<th>Line</th>
<th>Printed</th>
<th>Created</th>
</tr>
</thead>
<tbody id="unprinted-orders-table">
<!-- Data will be dynamically loaded here -->
</tbody>
</table>
</div>
</div>
</div>
<!-- JavaScript Libraries -->
<!-- JsBarcode library for real barcode generation -->
<script src="{{ url_for('static', filename='JsBarcode.all.min.js') }}"></script>
<!-- Add html2canvas library for capturing preview as image -->
<script src="{{ url_for('static', filename='html2canvas.min.js') }}"></script>
<!-- PATCHED QZ Tray library - works with custom server using pairing key authentication -->
<script src="{{ url_for('static', filename='qz-tray.js') }}"></script>
<!-- Original CDN version (disabled): <script src="https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.js"></script> -->
<script>
// Simplified notification system
function showNotification(message, type = 'info') {
const existingNotifications = document.querySelectorAll('.notification');
existingNotifications.forEach(n => n.remove());
const notification = document.createElement('div');
notification.className = `notification alert alert-${type === 'error' ? 'danger' : type === 'success' ? 'success' : type === 'warning' ? 'warning' : 'info'}`;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 450px;
padding: 15px;
border-radius: 5px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
`;
// Convert newlines to <br> tags for proper display
const formattedMessage = message.replace(/\n/g, '<br>');
notification.innerHTML = `
<div style="display: flex; align-items: flex-start; justify-content: space-between;">
<span style="flex: 1; padding-right: 10px; white-space: pre-wrap; font-family: monospace; font-size: 12px;">${formattedMessage}</span>
<button type="button" onclick="this.parentElement.parentElement.remove()" style="background: none; border: none; font-size: 20px; cursor: pointer; flex-shrink: 0;">&times;</button>
</div>
`;
document.body.appendChild(notification);
// Longer timeout for error messages
const timeout = type === 'error' ? 15000 : 5000;
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, timeout);
}
// Wait for DOM to be ready before attaching event listeners
document.addEventListener('DOMContentLoaded', function() {
console.log('🚀 DOM Content Loaded - Initializing page...');
// Database loading functionality
document.getElementById('check-db-btn').addEventListener('click', function() {
const button = this;
const originalText = button.textContent;
button.textContent = 'Loading...';
button.disabled = true;
fetch('/get_unprinted_orders')
.then(response => {
if (response.status === 403) {
return response.json().then(errorData => {
throw new Error(`Access Denied: ${errorData.error}`);
});
} else if (!response.ok) {
return response.text().then(text => {
throw new Error(`HTTP ${response.status}: ${text}`);
});
}
return response.json();
})
.then(data => {
console.log('Received data:', data);
const tbody = document.getElementById('unprinted-orders-table');
tbody.innerHTML = '';
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="15" style="text-align: center; padding: 20px; color: #28a745;"><strong>✅ All orders have been printed!</strong><br><small>No unprinted orders remaining.</small></td></tr>';
clearLabelPreview();
return;
}
data.forEach((order, index) => {
const tr = document.createElement('tr');
tr.dataset.orderId = order.id;
tr.dataset.orderIndex = index;
tr.style.cursor = 'pointer';
tr.innerHTML = `
<td style="font-size: 9px;">${order.id}</td>
<td style="font-size: 9px;"><strong>${order.comanda_productie}</strong></td>
<td style="font-size: 9px;">${order.cod_articol || '-'}</td>
<td style="font-size: 9px;">${order.descr_com_prod}</td>
<td style="text-align: right; font-weight: 600; font-size: 9px;">${order.cantitate}</td>
<td style="text-align: center; font-size: 9px;">
${order.data_livrare ? new Date(order.data_livrare).toLocaleDateString() : '-'}
</td>
<td style="text-align: center; font-size: 9px;">${order.dimensiune || '-'}</td>
<td style="font-size: 9px;">${order.com_achiz_client || '-'}</td>
<td style="text-align: right; font-size: 9px;">${order.nr_linie_com_client || '-'}</td>
<td style="font-size: 9px;">${order.customer_name || '-'}</td>
<td style="font-size: 9px;">${order.customer_article_number || '-'}</td>
<td style="font-size: 9px;">${order.open_for_order || '-'}</td>
<td style="text-align: right; font-size: 9px;">${order.line_number || '-'}</td>
<td style="text-align: center; font-size: 9px;">
${order.printed_labels == 1 ?
'<span style="color: #28a745; font-weight: bold;">✅ Yes</span>' :
'<span style="color: #dc3545;">❌ No</span>'}
</td>
<td style="font-size: 9px; color: #6c757d;">
${order.created_at ? new Date(order.created_at).toLocaleString() : '-'}
</td>
`;
tr.addEventListener('click', function() {
console.log('Row clicked:', order.id);
// Remove selection from other rows
document.querySelectorAll('.print-module-table tbody tr').forEach(row => {
row.classList.remove('selected');
const cells = row.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '';
cell.style.color = '';
});
});
// Select this row
this.classList.add('selected');
const cells = this.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '#007bff';
cell.style.color = 'white';
});
// Update label preview with selected order data
updateLabelPreview(order);
});
tbody.appendChild(tr);
});
// Auto-select first row
setTimeout(() => {
const firstRow = document.querySelector('.print-module-table tbody tr');
if (firstRow && !firstRow.querySelector('td[colspan]')) {
firstRow.click();
}
}, 100);
showNotification(`✅ Loaded ${data.length} unprinted orders`, 'success');
})
.catch(error => {
console.error('Error loading orders:', error);
const tbody = document.getElementById('unprinted-orders-table');
tbody.innerHTML = '<tr><td colspan="15" style="text-align: center; padding: 20px; color: #dc3545;"><strong>❌ Failed to load data</strong><br><small>' + error.message + '</small></td></tr>';
showNotification('❌ Failed to load orders: ' + error.message, 'error');
})
.finally(() => {
button.textContent = originalText;
button.disabled = false;
});
});
// Functions that don't need DOM elements (can be outside DOMContentLoaded)
// Update label preview with order data
function updateLabelPreview(order) {
const customerName = order.customer_name || 'N/A';
document.getElementById('customer-name-row').textContent = customerName;
// Update quantity ordered value
const quantity = order.cantitate || '0';
document.getElementById('quantity-ordered-value').textContent = quantity;
// Update client order info (Com.Achiz.Client - Nr. Linie)
const comAchizClient = order.com_achiz_client || '';
const nrLinie = order.nr_linie_com_client || '';
const clientOrderInfo = comAchizClient && nrLinie ? `${comAchizClient}-${nrLinie}` : 'N/A';
document.getElementById('client-order-info').textContent = clientOrderInfo;
// Update delivery date (using data_livrare column)
const deliveryDate = order.data_livrare ? new Date(order.data_livrare).toLocaleDateString() : 'N/A';
document.getElementById('delivery-date-value').textContent = deliveryDate;
// Update size (using dimensiune column)
const size = order.dimensiune || 'N/A';
document.getElementById('size-value').textContent = size;
// Update description (Descr. Com. Prod)
const description = order.descr_com_prod || 'N/A';
document.getElementById('description-value').textContent = description;
// Update article code (using cod_articol column)
const articleCode = order.cod_articol || 'N/A';
document.getElementById('article-code-value').textContent = articleCode;
// Update prod order (comanda_productie - cantitate)
const comandaProductie = order.comanda_productie || '';
const cantitate = order.cantitate || '';
const prodOrder = comandaProductie && cantitate ? `${comandaProductie}-${cantitate}` : 'N/A';
document.getElementById('prod-order-value').textContent = prodOrder;
// Update horizontal barcode with CP format (e.g., CP00000711/001)
// Show the first piece number (001) in preview
const horizontalBarcodeData = comandaProductie ? `${comandaProductie}/001` : 'N/A';
document.getElementById('barcode-text').textContent = horizontalBarcodeData;
// Generate horizontal barcode visual using JsBarcode
console.log('🔍 Attempting to generate horizontal barcode:', horizontalBarcodeData);
console.log('🔍 JsBarcode available?', typeof JsBarcode !== 'undefined');
if (horizontalBarcodeData !== 'N/A' && typeof JsBarcode !== 'undefined') {
try {
const barcodeElement = document.querySelector("#barcode-display");
console.log('🔍 Horizontal barcode element:', barcodeElement);
JsBarcode("#barcode-display", horizontalBarcodeData, {
format: "CODE128",
width: 2,
height: 40,
displayValue: false,
margin: 2
});
console.log('✅ Horizontal barcode generated successfully');
} catch (e) {
console.error('❌ Failed to generate horizontal barcode:', e);
}
} else {
console.warn('⚠️ Skipping horizontal barcode generation:',
horizontalBarcodeData === 'N/A' ? 'No data' : 'JsBarcode not loaded');
}
// Update vertical barcode with client order format (e.g., Abcderd/65)
const verticalBarcodeData = comAchizClient && nrLinie ? `${comAchizClient}/${nrLinie}` : '000000/00';
document.getElementById('vertical-barcode-text').textContent = verticalBarcodeData;
// Generate vertical barcode visual using JsBarcode (will be rotated by CSS)
console.log('🔍 Attempting to generate vertical barcode:', verticalBarcodeData);
if (verticalBarcodeData !== '000000/00' && typeof JsBarcode !== 'undefined') {
try {
const verticalElement = document.querySelector("#vertical-barcode-display");
console.log('🔍 Vertical barcode element:', verticalElement);
JsBarcode("#vertical-barcode-display", verticalBarcodeData, {
format: "CODE128",
width: 1.5,
height: 35,
displayValue: false,
margin: 2
});
console.log('✅ Vertical barcode generated successfully');
} catch (e) {
console.error('❌ Failed to generate vertical barcode:', e);
}
} else {
console.warn('⚠️ Skipping vertical barcode generation:',
verticalBarcodeData === '000000/00' ? 'Default value' : 'JsBarcode not loaded');
}
}
// Clear label preview when no orders are available
function clearLabelPreview() {
document.getElementById('customer-name-row').textContent = 'No orders available';
document.getElementById('quantity-ordered-value').textContent = '0';
document.getElementById('client-order-info').textContent = 'N/A';
document.getElementById('delivery-date-value').textContent = 'N/A';
document.getElementById('size-value').textContent = 'N/A';
document.getElementById('description-value').textContent = 'N/A';
document.getElementById('article-code-value').textContent = 'N/A';
document.getElementById('prod-order-value').textContent = 'N/A';
document.getElementById('barcode-text').textContent = 'N/A';
document.getElementById('vertical-barcode-text').textContent = '000000/00';
// Clear barcode SVGs
const horizontalBarcode = document.getElementById('barcode-display');
const verticalBarcode = document.getElementById('vertical-barcode-display');
if (horizontalBarcode) horizontalBarcode.innerHTML = '';
if (verticalBarcode) verticalBarcode.innerHTML = '';
}
// QZ Tray Integration
let qzTray = null;
let availablePrinters = [];
// Initialize QZ Tray connection
async function initializeQZTray() {
try {
console.log('🔍 Checking for QZ Tray...');
// Check if QZ Tray library is loaded
if (typeof qz === 'undefined') {
console.error('❌ QZ Tray library not loaded');
const errorMsg = '❌ QZ Tray Library Not Loaded\n\n' +
'The patched QZ Tray JavaScript library failed to load.\n' +
'Check the browser console for errors and refresh the page.\n\n' +
'Path: /static/qz-tray.js (patched version for pairing key auth)';
document.getElementById('qztray-status').textContent = 'Library Error';
document.getElementById('qztray-status').className = 'badge badge-danger';
showNotification(errorMsg, 'error');
return false;
}
console.log('✅ QZ Tray library loaded');
// Using PATCHED qz-tray.js that works with our custom QZ Tray server
// The patched library automatically skips certificate validation
// Our custom server uses ONLY pairing key (HMAC) authentication
console.log('🔒 Using patched qz-tray.js for pairing-key authentication...');
// No security setup needed - the patched library handles it
// Original qz-tray.js required setCertificatePromise, but our patch bypasses it
console.log('✅ Ready to connect to custom QZ Tray server');
console.log('🔌 Attempting to connect to QZ Tray on client PC...');
console.log('📍 Will try: ws://localhost:8181 (insecure) then wss://localhost:8182 (secure)');
// Set connection closed callback
qz.websocket.setClosedCallbacks(function() {
console.warn('⚠️ QZ Tray connection closed');
document.getElementById('qztray-status').textContent = 'Disconnected';
document.getElementById('qztray-status').className = 'badge badge-warning';
});
// Connect to QZ Tray running on client PC
console.log('⏳ Connecting...');
await qz.websocket.connect();
qzTray = qz;
const version = await qz.api.getVersion();
console.log('✅ QZ Tray connected successfully');
console.log('📋 QZ Tray Version:', version);
// Update status
document.getElementById('qztray-status').textContent = 'Ready';
document.getElementById('qztray-status').className = 'badge badge-success';
// Show printer selection, hide PDF option
document.getElementById('qztray-printer-selection').style.display = 'block';
document.getElementById('pdf-option-container').style.display = 'none';
// Load available printers
await loadQZTrayPrinters();
showNotification(`🖨️ QZ Tray v${version} connected successfully!`, 'success');
return true;
} catch (error) {
console.error('❌ QZ Tray connection failed:', error);
console.error('Error details:', {
message: error.message,
type: typeof error,
errorName: error.name,
stack: error.stack
});
// Detailed error messages based on actual error
let errorMsg = '❌ Cannot Connect to QZ Tray\n\n';
let statusText = 'Not Connected';
const errorStr = error.toString().toLowerCase();
const messageStr = (error.message || '').toLowerCase();
if (messageStr.includes('unable to establish') ||
errorStr.includes('unable to establish') ||
messageStr.includes('failed to connect') ||
errorStr.includes('websocket') ||
messageStr.includes('econnrefused')) {
errorMsg += '🔌 Connection Refused\n\n';
errorMsg += 'QZ Tray is not responding on this computer.\n\n';
errorMsg += '✅ Steps to fix:\n';
errorMsg += '1. Check if QZ Tray is installed on THIS computer\n';
errorMsg += '2. Look for QZ Tray icon in system tray (bottom-right)\n';
errorMsg += '3. If not running, start QZ Tray application\n';
errorMsg += '4. If installed but not working, restart QZ Tray\n';
errorMsg += '5. Download from: https://qz.io/download/\n\n';
errorMsg += '🔍 Technical: Trying to connect to ports 8181/8182';
statusText = 'Not Running';
} else if (messageStr.includes('certificate') ||
errorStr.includes('certificate') ||
messageStr.includes('security') ||
messageStr.includes('ssl') ||
messageStr.includes('tls')) {
errorMsg += '🔒 Certificate Security Issue\n\n';
errorMsg += 'QZ Tray uses a self-signed certificate for security.\n\n';
errorMsg += '✅ Steps to fix:\n';
errorMsg += '1. Open new tab: https://localhost:8182\n';
errorMsg += '2. Accept/Trust the security certificate\n';
errorMsg += '3. Come back and click "Reconnect to QZ Tray"\n\n';
errorMsg += '🔍 This is normal and safe for QZ Tray';
statusText = 'Certificate Error';
} else {
errorMsg += '⚠️ Unexpected Error\n\n';
errorMsg += 'Error: ' + error.message + '\n\n';
errorMsg += '🔍 Troubleshooting:\n';
errorMsg += '1. Open browser console (F12) for details\n';
errorMsg += '2. Click "Test Connection" for diagnostics\n';
errorMsg += '3. Make sure QZ Tray is running\n';
errorMsg += '4. Try restarting your browser';
}
document.getElementById('qztray-status').textContent = statusText;
document.getElementById('qztray-status').className = 'badge badge-danger';
// QZ Tray not available - show PDF option as fallback
document.getElementById('qztray-printer-selection').style.display = 'none';
document.getElementById('pdf-option-container').style.display = 'block';
// Switch to PDF method since QZ Tray is unavailable
document.getElementById('pdfGenerate').checked = true;
document.getElementById('qzTrayPrint').disabled = true;
showNotification(errorMsg, 'error');
return false;
}
}
// Manual test connection function
async function testQZConnection() {
console.log('🔍 ========== QZ TRAY CONNECTION TEST ==========');
const statusElement = document.getElementById('qztray-status');
const originalStatus = statusElement.textContent;
statusElement.textContent = 'Testing...';
statusElement.className = 'badge badge-warning';
let testResults = [];
try {
// Test 1: Check if library is loaded
console.log('Test 1: Checking library...');
if (typeof qz === 'undefined') {
testResults.push('❌ Test 1: Library NOT loaded from CDN');
throw new Error('QZ Tray library not loaded from CDN');
}
testResults.push('✅ Test 1: Library loaded successfully');
console.log('✅ Test 1 PASSED: Library loaded from local (patched version)');
// Using patched qz-tray.js - no security setup needed
console.log('🔒 Using patched library for custom QZ Tray...');
// Test 2: Check if already connected
console.log('Test 2: Checking existing connection...');
if (qz.websocket.isActive()) {
testResults.push('✅ Test 2: Already connected!');
console.log('✅ Test 2 PASSED: Already connected');
const version = await qz.api.getVersion();
testResults.push(`✅ Version: ${version}`);
console.log(`📋 QZ Tray Version: ${version}`);
const printers = await qz.printers.find();
testResults.push(`✅ Found ${printers.length} printer(s)`);
console.log(`🖨️ Printers found: ${printers.length}`);
showNotification(testResults.join('\n'), 'success');
statusElement.textContent = 'Connected';
statusElement.className = 'badge badge-success';
console.log('========== TEST COMPLETED: ALL PASSED ==========');
return;
}
testResults.push('⚠️ Test 2: Not currently connected');
console.log('⚠️ Test 2: Not connected, will attempt connection...');
// Test 3: Try to connect
console.log('Test 3: Attempting WebSocket connection...');
testResults.push('🔌 Test 3: Connecting to QZ Tray...');
console.log('🔌 Connecting to WebSocket...');
await qz.websocket.connect();
testResults.push('✅ Test 3: WebSocket connected!');
console.log('✅ Test 3 PASSED: WebSocket connected');
// Test 4: Get version
console.log('Test 4: Getting QZ Tray version...');
const version = await qz.api.getVersion();
testResults.push(`✅ Test 4: QZ Tray v${version}`);
console.log(`✅ Test 4 PASSED: Version ${version}`);
// Test 5: List printers
console.log('Test 5: Fetching printer list...');
const printers = await qz.printers.find();
testResults.push(`✅ Test 5: Found ${printers.length} printer(s)`);
console.log(`✅ Test 5 PASSED: ${printers.length} printers found`);
if (printers.length > 0) {
console.log('📋 Available printers:', printers);
testResults.push('📋 Printers: ' + printers.slice(0, 3).join(', ') + (printers.length > 3 ? '...' : ''));
}
// Success!
qzTray = qz;
statusElement.textContent = 'Connected';
statusElement.className = 'badge badge-success';
console.log('========== TEST COMPLETED: ALL PASSED ==========');
showNotification(testResults.join('\n') + '\n\n✅ All tests passed!', 'success');
// Reload printers
await loadQZTrayPrinters();
} catch (error) {
console.error('❌ TEST FAILED:', error);
console.error('Error type:', typeof error);
console.error('Error name:', error.name);
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
statusElement.textContent = originalStatus;
statusElement.className = 'badge badge-danger';
const errorStr = error.toString().toLowerCase();
const messageStr = (error.message || '').toLowerCase();
let errorMsg = '❌ Connection Test Results:\n\n';
errorMsg += testResults.join('\n') + '\n\n';
if (typeof qz === 'undefined') {
errorMsg += '❌ FAILED: Library Load Error\n\n';
errorMsg += '📚 The QZ Tray JavaScript library did not load.\n\n';
errorMsg += 'Fix:\n';
errorMsg += '• Check browser console for errors (F12)\n';
errorMsg += '• Refresh the page (F5)\n';
errorMsg += '• Check if library loaded:\n';
errorMsg += ' /static/qz-tray.js (patched version)';
} else if (messageStr.includes('unable to establish') ||
errorStr.includes('unable to establish') ||
messageStr.includes('failed to connect') ||
messageStr.includes('websocket') ||
errorStr.includes('websocket error')) {
errorMsg += '❌ FAILED: Cannot Connect to QZ Tray\n\n';
errorMsg += '🔌 QZ Tray is not running on this computer.\n\n';
errorMsg += 'Fix:\n';
errorMsg += '1. Check if QZ Tray is installed\n';
errorMsg += '2. Look in system tray (bottom-right) for QZ icon\n';
errorMsg += '3. If not there, launch QZ Tray app\n';
errorMsg += '4. Download: https://qz.io/download/\n\n';
errorMsg += '💡 QZ Tray must be running on THIS computer,\n';
errorMsg += ' not on the server!';
} else if (messageStr.includes('certificate') ||
errorStr.includes('certificate') ||
messageStr.includes('security')) {
errorMsg += '❌ FAILED: Certificate Error\n\n';
errorMsg += '🔒 Browser security blocking connection.\n\n';
errorMsg += 'Fix:\n';
errorMsg += '1. Open: https://localhost:8182\n';
errorMsg += '2. Click "Advanced" or "Proceed"\n';
errorMsg += '3. Accept the certificate\n';
errorMsg += '4. Return here and test again\n\n';
errorMsg += '💡 Self-signed certificates are normal for QZ Tray';
} else {
errorMsg += '❌ FAILED: Unexpected Error\n\n';
errorMsg += 'Error: ' + error.message + '\n\n';
errorMsg += 'Next steps:\n';
errorMsg += '1. Check browser console (F12)\n';
errorMsg += '2. Verify QZ Tray is running\n';
errorMsg += '3. Restart browser and QZ Tray\n';
errorMsg += '4. Check firewall settings';
}
console.log('========== TEST COMPLETED: FAILED ==========');
showNotification(errorMsg, 'error');
}
}
// Load available printers from QZ Tray
async function loadQZTrayPrinters() {
try {
if (!qzTray) return;
const printers = await qzTray.printers.find();
availablePrinters = printers;
const printerSelect = document.getElementById('qztray-printer-select');
printerSelect.innerHTML = '<option value="">Select a printer...</option>';
printers.forEach(printer => {
const option = document.createElement('option');
option.value = printer;
option.textContent = printer;
printerSelect.appendChild(option);
});
console.log(`📄 Found ${printers.length} printers:`, printers);
// Auto-select first thermal printer if available
const thermalPrinter = printers.find(p =>
p.toLowerCase().includes('thermal') ||
p.toLowerCase().includes('label') ||
p.toLowerCase().includes('zebra') ||
p.toLowerCase().includes('epson')
);
if (thermalPrinter) {
printerSelect.value = thermalPrinter;
}
} catch (error) {
console.error('Error loading printers:', error);
showNotification('⚠️ Failed to load printers: ' + error.message, 'warning');
}
}
// Generate PDF and send to thermal printer
async function generatePDFAndPrint(selectedPrinter, orderData, pieceNumber, totalPieces) {
try {
console.log(`📄 Generating PDF for thermal printing... (${pieceNumber}/${totalPieces})`);
// Prepare data for PDF generation
const pdfData = {
...orderData,
quantity: 1, // Single label per print job
piece_number: pieceNumber,
total_pieces: totalPieces
};
// Call backend to generate PDF
const response = await fetch('/generate_label_pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(pdfData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to generate PDF');
}
// Get PDF as blob
const pdfBlob = await response.blob();
// Convert blob to base64 for QZ Tray
const pdfBase64 = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
// Remove data:application/pdf;base64, prefix
const base64 = reader.result.split(',')[1];
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(pdfBlob);
});
console.log(`🖨️ Sending PDF to printer ${selectedPrinter}...`);
// Configure QZ Tray for PDF printing with explicit paper size
const config = qz.configs.create(selectedPrinter, {
scaleContent: false,
rasterize: false,
size: { width: 80, height: 100 },
units: 'mm',
margins: { top: 0, right: 0, bottom: 0, left: 0 }
});
// Prepare PDF data for QZ Tray
const data = [{
type: 'pdf',
format: 'base64',
data: pdfBase64
}];
await qz.print(config, data);
console.log(`✅ PDF sent to printer successfully (${pieceNumber}/${totalPieces})`);
} catch (error) {
console.error('❌ Error generating/printing PDF:', error);
throw error;
}
}
// Helper function to convert array buffer to base64
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
const binary = bytes.reduce((data, byte) => data + String.fromCharCode(byte), '');
return btoa(binary);
}
// Helper function to convert blob to base64 (kept for compatibility)
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result.split(',')[1]; // Remove data URL prefix
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// Update preview with order data for specific piece
function updatePreview(orderData, pieceNumber, totalPieces) {
// Extract and format the data
const {
customer_name = 'N/A',
cantitate = '0',
com_achiz_client = '',
nr_linie_com_client = '',
data_livrare = null,
dimensiune = 'N/A',
descr_com_prod = 'N/A',
cod_articol = 'N/A',
comanda_productie = ''
} = orderData;
const deliveryDate = data_livrare ? new Date(data_livrare).toLocaleDateString() : 'N/A';
const clientOrder = com_achiz_client && nr_linie_com_client ? `${com_achiz_client}-${nr_linie_com_client}` : 'N/A';
const horizontalBarcode = comanda_productie ? `${comanda_productie}-${String(pieceNumber).padStart(3, '0')}` : 'N/A';
const verticalBarcode = clientOrder;
const prodOrder = comanda_productie && cantitate ? `${comanda_productie}-${cantitate}` : 'N/A';
// Update preview elements with correct IDs
const customerRowElement = document.getElementById('customer-name-row');
if (customerRowElement) customerRowElement.textContent = customer_name;
const quantityElement = document.getElementById('quantity-ordered-value');
if (quantityElement) quantityElement.textContent = cantitate;
const clientOrderElement = document.getElementById('client-order-info');
if (clientOrderElement) clientOrderElement.textContent = clientOrder;
const deliveryDateElement = document.getElementById('delivery-date-value');
if (deliveryDateElement) deliveryDateElement.textContent = deliveryDate;
const descriptionElement = document.getElementById('description-value');
if (descriptionElement) descriptionElement.textContent = descr_com_prod;
const sizeElement = document.getElementById('size-value');
if (sizeElement) sizeElement.textContent = dimensiune;
const articleCodeElement = document.getElementById('article-code-value');
if (articleCodeElement) articleCodeElement.textContent = cod_articol;
const prodOrderElement = document.getElementById('prod-order-value');
if (prodOrderElement) prodOrderElement.textContent = prodOrder;
const barcodeTextElement = document.getElementById('barcode-text');
if (barcodeTextElement) barcodeTextElement.textContent = horizontalBarcode;
const verticalBarcodeTextElement = document.getElementById('vertical-barcode-text');
if (verticalBarcodeTextElement) verticalBarcodeTextElement.textContent = verticalBarcode;
}
function generateHTMLLabel(orderData, pieceNumber, totalPieces) {
const {
customer_name = 'N/A',
cantitate = '0',
com_achiz_client = '',
nr_linie_com_client = '',
data_livrare = null,
dimensiune = 'N/A',
descr_com_prod = 'N/A',
cod_articol = 'N/A',
comanda_productie = ''
} = orderData;
// Format data for label (matching preview format)
const deliveryDate = data_livrare ? new Date(data_livrare).toLocaleDateString() : 'N/A';
const clientOrder = com_achiz_client && nr_linie_com_client ? `${com_achiz_client}-${nr_linie_com_client}` : 'N/A';
const horizontalBarcode = comanda_productie ? `${comanda_productie}-${String(pieceNumber).padStart(3, '0')}` : 'N/A';
const verticalBarcode = clientOrder;
const prodOrder = comanda_productie && cantitate ? `${comanda_productie}-${cantitate}` : 'N/A';
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@page {
size: 80mm 100mm;
margin: 0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
width: 80mm;
height: 105mm;
position: relative;
background: white;
font-size: 12px;
}
.label-container {
position: absolute;
top: 5mm;
left: 2mm;
width: 76mm;
height: 100mm;
border: 2px solid black;
display: flex;
flex-direction: column;
}
.header {
text-align: center;
font-size: 14px;
font-weight: bold;
padding: 3mm 2mm;
border-bottom: 1px solid black;
}
.customer-row {
text-align: center;
font-size: 12px;
padding: 2mm;
border-bottom: 1px solid black;
min-height: 8mm;
display: flex;
align-items: center;
justify-content: center;
}
.data-section {
display: flex;
flex: 1;
flex-direction: column;
}
.data-row {
display: flex;
height: 8mm;
border-bottom: 1px solid black;
}
.data-row.double {
height: 16mm;
}
.label-column {
width: 30mm;
padding: 1mm 2mm;
font-size: 8px;
border-right: 1px solid black;
display: flex;
align-items: center;
}
.value-column {
flex: 1;
padding: 1mm 2mm;
font-size: 10px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.description-value {
font-size: 8px !important;
line-height: 1.2;
overflow: hidden;
}
.barcode-section {
position: absolute;
bottom: 2mm;
left: 2mm;
width: 50mm;
height: 15mm;
border: 2px solid black;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: white;
}
.barcode-visual {
font-family: 'Courier New', monospace;
font-size: 16px;
font-weight: bold;
line-height: 1;
margin-bottom: 2mm;
}
.barcode-text {
font-family: 'Courier New', monospace;
font-size: 8px;
font-weight: bold;
}
.vertical-barcode {
position: absolute;
right: 2mm;
top: 25mm;
width: 12mm;
height: 60mm;
border: 2px solid black;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: white;
}
.vertical-bars {
font-family: 'Courier New', monospace;
font-size: 12px;
font-weight: bold;
writing-mode: vertical-rl;
text-orientation: sideways;
margin: 2mm 0;
}
.vertical-text {
font-family: 'Courier New', monospace;
font-size: 6px;
font-weight: bold;
writing-mode: vertical-rl;
text-orientation: mixed;
transform: rotate(180deg);
}
</style>
</head>
<body>
<div class="label-container">
<div class="header">INNOFA ROMANIA SRL</div>
<div class="customer-row">${customer_name}</div>
<div class="data-section">
<div class="data-row">
<div class="label-column">Quantity ordered</div>
<div class="value-column">${cantitate}</div>
</div>
<div class="data-row">
<div class="label-column">Customer order</div>
<div class="value-column">${clientOrder}</div>
</div>
<div class="data-row">
<div class="label-column">Delivery date</div>
<div class="value-column">${deliveryDate}</div>
</div>
<div class="data-row double">
<div class="label-column">Product description</div>
<div class="value-column description-value">${descr_com_prod}</div>
</div>
<div class="data-row">
<div class="label-column">Size</div>
<div class="value-column">${dimensiune}</div>
</div>
<div class="data-row">
<div class="label-column">Article code</div>
<div class="value-column">${cod_articol}</div>
</div>
<div class="data-row">
<div class="label-column">Prod. order</div>
<div class="value-column">${prodOrder}</div>
</div>
</div>
</div>
<div class="barcode-section">
<div class="barcode-visual">||||| || ||| || ||||| ||| || |||||</div>
<div class="barcode-text">${horizontalBarcode}</div>
</div>
<div class="vertical-barcode">
<div class="vertical-bars">| | | | | | |</div>
<div class="vertical-text">${verticalBarcode}</div>
</div>
</body>
</html>`;
return htmlContent;
}
// Handle QZ Tray printing with enhanced controller
async function handleQZTrayPrint(selectedRow) {
try {
if (!qzTray) {
await initializeQZTray();
if (!qzTray) {
throw new Error('QZ Tray not available');
}
}
const selectedPrinter = document.getElementById('qztray-printer-select').value;
if (!selectedPrinter) {
showNotification('⚠️ Please select a printer first', 'warning');
return;
}
// Extract order data from row
const cells = selectedRow.querySelectorAll('td');
const orderData = {
id: cells[0].textContent,
comanda_productie: cells[1].textContent.trim(),
cod_articol: cells[2].textContent.trim(),
descr_com_prod: cells[3].textContent.trim(),
cantitate: parseInt(cells[4].textContent.trim()),
data_livrare: cells[5].textContent.trim(),
dimensiune: cells[6].textContent.trim(),
com_achiz_client: cells[7].textContent.trim(),
nr_linie_com_client: cells[8].textContent.trim(),
customer_name: cells[9].textContent.trim()
};
const quantity = orderData.cantitate;
console.log(`🖨️ Printing ${quantity} labels via QZ Tray to ${selectedPrinter}`);
showNotification(`🖨️ Printing ${quantity} labels...`, 'info');
// Print each label sequentially
for (let i = 1; i <= quantity; i++) {
console.log(`Printing label ${i} of ${quantity}...`);
try {
// Generate PDF and send to printer
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
console.log(`✓ Label ${i} printed successfully`);
} catch (printError) {
console.error(`✗ Label ${i} failed:`, printError);
throw new Error(`Label ${i} failed: ${printError.message}`);
}
// Small delay between labels
if (i < quantity) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
// All labels printed successfully
console.log('All labels completed successfully');
showNotification(`✅ Successfully printed ${quantity} labels!`, 'success');
// Update database to mark order as printed
try {
const updateResponse = await fetch(`/update_printed_status/${orderData.id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!updateResponse.ok) {
console.error('Failed to update printed status in database');
showNotification('⚠️ Labels printed but database update failed', 'warning');
} else {
console.log('Database updated successfully');
}
} catch (dbError) {
console.error('Database update error:', dbError);
showNotification('⚠️ Labels printed but database update failed', 'warning');
}
// Refresh table to show updated status
document.getElementById('check-db-btn').click();
} catch (error) {
console.error('QZ Tray print error:', error);
showNotification('❌ QZ Tray print error: ' + error.message, 'error');
}
}
// Print Button Handler - Routes to appropriate print method
document.getElementById('print-label-btn').addEventListener('click', async function(e) {
e.preventDefault();
// Get selected order
const selectedRow = document.querySelector('.print-module-table tbody tr.selected');
if (!selectedRow) {
showNotification('⚠️ Please select an order first from the table below.', 'warning');
return;
}
// Get selected print method
const printMethod = document.querySelector('input[name="printMethod"]:checked').value;
if (printMethod === 'qztray') {
await handleQZTrayPrint(selectedRow);
} else {
handlePDFGeneration(selectedRow);
}
});
// Handle PDF generation
function handlePDFGeneration(selectedRow) {
const orderId = selectedRow.dataset.orderId;
const quantityCell = selectedRow.querySelector('td:nth-child(5)');
const quantity = quantityCell ? parseInt(quantityCell.textContent) : 1;
const prodOrderCell = selectedRow.querySelector('td:nth-child(2)');
const prodOrder = prodOrderCell ? prodOrderCell.textContent.trim() : 'N/A';
const button = document.getElementById('print-label-btn');
const originalText = button.textContent;
button.textContent = 'Generating PDF...';
button.disabled = true;
console.log(`Generating PDF for order ${orderId} with ${quantity} labels`);
// Generate PDF with paper-saving mode enabled (optimized for thermal printers)
fetch(`/generate_labels_pdf/${orderId}/true`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
})
.then(blob => {
// Create blob URL for PDF
const url = window.URL.createObjectURL(blob);
// Create download link for PDF
const a = document.createElement('a');
a.href = url;
a.download = `labels_${prodOrder}_${quantity}pcs.pdf`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Also open PDF in new tab for printing
const printWindow = window.open(url, '_blank');
if (printWindow) {
printWindow.focus();
// Wait for PDF to load, then show print dialog
setTimeout(() => {
printWindow.print();
// Clean up blob URL after print dialog is shown
setTimeout(() => {
window.URL.revokeObjectURL(url);
}, 2000);
}, 1500);
} else {
// If popup was blocked, clean up immediately
setTimeout(() => {
window.URL.revokeObjectURL(url);
}, 1000);
}
// Show success message
showNotification(`✅ PDF generated successfully!\n📊 Order: ${prodOrder}\n📦 Labels: ${quantity} pieces`, 'success');
// Refresh the orders table to reflect printed status
setTimeout(() => {
document.getElementById('check-db-btn').click();
}, 1000);
})
.catch(error => {
console.error('Error generating PDF:', error);
showNotification('❌ Failed to generate PDF labels. Error: ' + error.message, 'error');
})
.finally(() => {
// Reset button state
button.textContent = originalText;
button.disabled = false;
});
}
// UI Control Functions
function updatePrintMethodUI() {
const printMethod = document.querySelector('input[name="printMethod"]:checked').value;
const printerSelection = document.getElementById('qztray-printer-selection');
const printButton = document.getElementById('print-label-btn');
if (printMethod === 'qztray') {
// QZ Tray is selected - printer selection should be visible if QZ Tray is connected
// (visibility is controlled by initializeQZTray based on connection status)
printButton.textContent = '🖨️ Print Labels';
printButton.className = 'btn btn-primary';
} else {
// PDF is selected
printerSelection.style.display = 'none';
printButton.textContent = '📄 Generate PDF';
printButton.className = 'btn btn-success';
}
}
// Close the DOMContentLoaded block that was started earlier
// Fetch pairing keys and populate dropdown (only show if multiple keys)
fetch('/get_pairing_keys')
.then(response => response.json())
.then(keys => {
const select = document.getElementById('client-select');
const selectContainer = document.getElementById('client-select-container');
if (keys.length === 0) {
console.warn('⚠️ No pairing keys found');
return;
}
if (keys.length === 1) {
// Only one key - use it automatically, don't show dropdown
window.selectedPairingKey = keys[0].pairing_key;
console.log(`✅ Using single pairing key for: ${keys[0].printer_name}`);
selectContainer.style.display = 'none';
} else {
// Multiple keys - show dropdown for selection
select.innerHTML = '';
keys.forEach(key => {
const option = document.createElement('option');
option.value = key.pairing_key;
option.textContent = key.printer_name;
select.appendChild(option);
});
window.selectedPairingKey = keys[0].pairing_key;
selectContainer.style.display = 'block';
console.log(`📋 ${keys.length} pairing keys available - dropdown shown`);
}
})
.catch(error => {
console.error('❌ Failed to fetch pairing keys:', error);
});
// Update pairing key on selection
document.getElementById('client-select').addEventListener('change', function() {
window.selectedPairingKey = this.value;
console.log(`🔑 Pairing key changed: ${this.options[this.selectedIndex].text}`);
});
// Add change listeners to radio buttons
document.querySelectorAll('input[name="printMethod"]').forEach(radio => {
radio.addEventListener('change', updatePrintMethodUI);
});
// Initialize UI
updatePrintMethodUI();
// Initialize QZ Tray
setTimeout(initializeQZTray, 1000);
// Load orders
setTimeout(() => {
document.getElementById('check-db-btn').click();
}, 500);
}); // End DOMContentLoaded
</script>
{% endblock %}