Files
quality_recticel/py_app/app/templates/print_module.html
Scheianu Ionut 6a184ce191 Fix modal display issue - add debugging and async/await
- Made print button handler async and await handleQZTrayPrint
- Added console.log debugging to track modal element and class changes
- This should fix the modal not appearing during print operations
2025-10-02 18:06:32 +03:00

1825 lines
76 KiB
HTML

{% extends "base.html" %}
{% block head %}
<!-- JsBarcode library for real barcode generation -->
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<style>
#label-preview {
background: #fafafa;
position: relative;
overflow: hidden;
}
/* 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 */
.print-progress-modal {
display: none !important;
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
justify-content: center;
align-items: center;
}
.print-progress-modal.show {
display: flex !important;
}
.print-progress-content {
background-color: white;
padding: 30px;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
min-width: 500px;
max-width: 600px;
text-align: center;
}
.print-progress-content h3 {
margin: 0 0 20px 0;
color: #333;
font-size: 24px;
}
.progress-info {
margin-bottom: 15px;
color: #666;
font-size: 16px;
min-height: 24px;
}
.progress-bar-container {
width: 100%;
height: 35px;
background-color: #e9ecef;
border-radius: 18px;
overflow: hidden;
margin-bottom: 15px;
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.15);
position: relative;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #28a745 0%, #20c997 100%);
width: 0%;
transition: width 0.4s ease;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 14px;
}
.progress-bar.error {
background: linear-gradient(90deg, #dc3545 0%, #c82333 100%);
}
.progress-bar.paused {
background: linear-gradient(90deg, #ffc107 0%, #ff9800 100%);
}
.progress-details {
font-size: 20px;
font-weight: bold;
color: #28a745;
margin-bottom: 15px;
}
.progress-details.error {
color: #dc3545;
}
.print-status-log {
max-height: 150px;
overflow-y: auto;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 10px;
margin: 15px 0;
text-align: left;
font-family: monospace;
font-size: 12px;
}
.print-status-log div {
padding: 3px 0;
color: #495057;
}
.print-status-log div.success {
color: #28a745;
}
.print-status-log div.error {
color: #dc3545;
font-weight: bold;
}
.print-status-log div.warning {
color: #ffc107;
}
.print-control-buttons {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 20px;
}
.print-control-btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.print-control-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.print-control-btn.pause {
background: #ffc107;
color: #000;
}
.print-control-btn.resume {
background: #28a745;
color: white;
}
.print-control-btn.reprint {
background: #17a2b8;
color: white;
}
.print-control-btn.cancel {
background: #dc3545;
color: white;
}
.print-control-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</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>
<!-- Add link to pairing key management -->
<div style="width: 100%; text-align: center; margin-bottom: 10px;">
<a href="{{ url_for('main.download_extension') }}" class="btn btn-info btn-sm" target="_blank">🔑 Manage Pairing Keys</a>
</div>
<!-- Client/Printer selection dropdown -->
<div style="width: 100%; text-align: center; margin-bottom: 10px;">
<label for="client-select" style="font-weight: 600;">Select Printer/Client:</label>
<select id="client-select" class="form-control form-control-sm" style="width: 80%; margin: 0 auto;"></select>
</div>
<!-- Display pairing key for selected client -->
<div id="pairing-key-display" style="width: 100%; text-align: center; margin-bottom: 15px; font-size: 13px; color: #007bff; font-family: monospace;"></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 with 2mm side margins -->
<div id="barcode-frame" style="position: absolute; top: 395px; left: 7.56px; width: 295.44px; 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: 280px; 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 of the label -->
<div id="vertical-barcode-frame" style="position: absolute; top: 70px; left: 245px; width: 50px; height: 309.96px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<!-- Vertical Code 128 Barcode representation -->
<svg id="vertical-barcode-display" style="width: 45px; height: 280px;"></svg>
<!-- Vertical barcode text -->
<div id="vertical-barcode-text" style="font-size: 6px; font-family: 'Courier New', monospace; margin-top: 5px; text-align: center; font-weight: bold; writing-mode: vertical-rl; text-orientation: mixed;">
<!-- 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;">
<label style="font-size: 12px; font-weight: 600; color: #495057; margin-bottom: 8px; display: block;">
📄 Print Method:
</label>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="printMethod" id="qzTrayPrint" value="qztray">
<label class="form-check-label" for="qzTrayPrint" style="font-size: 11px; line-height: 1.3;">
<strong>QZ Tray Direct Print</strong> <span id="qztray-status" class="badge badge-secondary">Checking...</span><br>
<span class="text-muted">Direct print to thermal label printer</span>
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="printMethod" id="pdfGenerate" value="pdf" checked>
<label class="form-check-label" for="pdfGenerate" style="font-size: 11px; line-height: 1.3;">
<strong>Generate PDF</strong><br>
<span class="text-muted">Create PDF for manual printing (recommended)</span>
</label>
</div>
</div>
<!-- Printer Selection for QZ Tray -->
<div id="qztray-printer-selection" style="margin-bottom: 15px; display: none;">
<label style="font-size: 12px; font-weight: 600; color: #495057; margin-bottom: 5px; display: block;">
🖨️ Select Printer:
</label>
<select id="qztray-printer-select" class="form-control form-control-sm" style="font-size: 11px; padding: 4px 8px;">
<option value="">Loading printers...</option>
</select>
<small class="text-muted" style="font-size: 10px;">Choose your thermal label printer</small>
</div>
<!-- Print Button -->
<div style="width: 100%; text-align: center; margin-bottom: 15px;">
<button id="print-label-btn" class="btn btn-success" style="font-size: 14px; padding: 10px 30px; border-radius: 6px; font-weight: 600;">
📄 Generate PDF Labels
</button>
</div>
<!-- Print Information -->
<div style="width: 100%; text-align: center; color: #6c757d; font-size: 11px; line-height: 1.4;">
<div style="margin-bottom: 5px;">Creates sequential labels based on quantity</div>
<small>(e.g., CP00000711-001 to CP00000711-063)</small>
</div>
<!-- QZ Tray Installation Info -->
<div id="qztray-info" style="width: 100%; margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef; display: none;">
<div style="background: #e8f4fd; border: 1px solid #bee5eb; border-radius: 6px; padding: 12px; margin-bottom: 10px;">
<div style="font-size: 11px; color: #0c5460; margin-bottom: 8px;">
<strong>🖨️ QZ Tray Direct Printing</strong>
</div>
<div style="font-size: 10px; color: #495057; margin-bottom: 10px; line-height: 1.3;">
Professional printing solution • ZPL thermal labels • Direct hardware access
</div>
<div style="margin-bottom: 10px;">
<button onclick="initializeQZTray()" class="btn btn-primary btn-sm" style="font-size: 10px; padding: 4px 12px;">
🔄 Reconnect to QZ Tray
</button>
<button onclick="testQZConnection()" class="btn btn-info btn-sm" style="font-size: 10px; padding: 4px 12px;">
🔍 Test Connection
</button>
</div>
<a href="https://qz.io/download/" target="_blank" class="btn btn-secondary btn-sm" style="font-size: 10px; padding: 4px 12px; text-decoration: none;">
📥 Download QZ Tray (Free)
</a>
<div style="font-size: 9px; color: #6c757d; margin-top: 8px; line-height: 1.2;">
<strong>Setup:</strong> 1. Install QZ Tray → 2. Start the service → 3. Click "Reconnect"
</div>
</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>
<!-- Printing Progress Modal -->
<div id="print-progress-modal" class="print-progress-modal">
<div class="print-progress-content">
<h3>🖨️ Print Controller</h3>
<div class="progress-info">
<span id="progress-text">Preparing to print...</span>
</div>
<div class="progress-bar-container">
<div id="progress-bar" class="progress-bar">0%</div>
</div>
<div class="progress-details">
<span id="progress-count">0 / 0</span>
</div>
<!-- Print Status Log -->
<div class="print-status-log" id="print-status-log">
<div>Waiting to start...</div>
</div>
<!-- Control Buttons -->
<div class="print-control-buttons">
<button id="pause-print-btn" class="print-control-btn pause" style="display: none;">
⏸️ Pause
</button>
<button id="resume-print-btn" class="print-control-btn resume" style="display: none;">
▶️ Resume
</button>
<button id="reprint-last-btn" class="print-control-btn reprint" style="display: none;">
🔄 Reprint Last
</button>
<button id="cancel-print-btn" class="print-control-btn cancel" style="display: none;">
❌ Cancel
</button>
</div>
</div>
</div>
<!-- QZ Tray JavaScript Library -->
<!-- Add html2canvas library for capturing preview as image -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/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);
}
// 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;
});
});
// Fetch pairing keys and populate dropdown
fetch('/get_pairing_keys')
.then(response => response.json())
.then(keys => {
const select = document.getElementById('client-select');
select.innerHTML = '';
keys.forEach(key => {
const option = document.createElement('option');
option.value = key.pairing_key;
option.textContent = key.printer_name;
select.appendChild(option);
});
// Show first key by default
if (keys.length > 0) {
document.getElementById('pairing-key-display').textContent = 'Pairing Key: ' + keys[0].pairing_key;
}
});
// Update pairing key display on selection
document.getElementById('client-select').addEventListener('change', function() {
document.getElementById('pairing-key-display').textContent = 'Pairing Key: ' + this.value;
});
// 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;
// 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;
}
// 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';
}
// 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 = 'Connected';
document.getElementById('qztray-status').className = 'badge badge-success';
// 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';
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
const config = qz.configs.create(selectedPrinter, {
scaleContent: false,
rasterize: false
});
// 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 110mm;
margin: 0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
width: 80mm;
height: 110mm;
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
let printController = {
isPaused: false,
isCancelled: false,
currentLabel: 0,
totalLabels: 0,
lastPrintedLabel: 0,
failedLabels: [],
orderData: null,
printerName: null
};
function addLogEntry(message, type = 'info') {
const log = document.getElementById('print-status-log');
const entry = document.createElement('div');
entry.className = type;
const timestamp = new Date().toLocaleTimeString();
entry.textContent = `[${timestamp}] ${message}`;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
function updateProgressBar(current, total, status = '') {
const progressBar = document.getElementById('progress-bar');
const progressCount = document.getElementById('progress-count');
const progressText = document.getElementById('progress-text');
const percentage = Math.round((current / total) * 100);
progressBar.style.width = `${percentage}%`;
progressBar.textContent = `${percentage}%`;
progressCount.textContent = `${current} / ${total}`;
if (status) {
progressText.textContent = status;
}
}
async function handleQZTrayPrint(selectedRow) {
const modal = document.getElementById('print-progress-modal');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const log = document.getElementById('print-status-log');
const pauseBtn = document.getElementById('pause-print-btn');
const resumeBtn = document.getElementById('resume-print-btn');
const reprintBtn = document.getElementById('reprint-last-btn');
const cancelBtn = document.getElementById('cancel-print-btn');
// Debug: Check if modal exists
console.log('🔍 Modal element:', modal);
console.log('🔍 Modal classList before:', modal ? modal.classList.toString() : 'MODAL NOT FOUND');
// Reset controller state
printController = {
isPaused: false,
isCancelled: false,
currentLabel: 0,
totalLabels: 0,
lastPrintedLabel: 0,
failedLabels: [],
orderData: null,
printerName: null
};
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;
printController.orderData = orderData;
printController.printerName = selectedPrinter;
printController.totalLabels = quantity;
console.log(`🖨️ Printing ${quantity} labels via QZ Tray to ${selectedPrinter}`);
// Show modal with show class for proper display
console.log('👉 Adding show class to modal...');
modal.classList.add('show');
console.log('🔍 Modal classList after:', modal.classList.toString());
console.log('🔍 Modal computed display:', window.getComputedStyle(modal).display);
log.innerHTML = '';
addLogEntry(`Starting print job: ${quantity} labels`, 'info');
addLogEntry(`Printer: ${selectedPrinter}`, 'info');
addLogEntry(`Order: ${orderData.comanda_productie}`, 'info');
updateProgressBar(0, quantity, 'Preparing to print...');
progressBar.classList.remove('error', 'paused');
// Show control buttons
pauseBtn.style.display = 'inline-block';
cancelBtn.style.display = 'inline-block';
reprintBtn.style.display = 'none';
resumeBtn.style.display = 'none';
// Setup button handlers
pauseBtn.onclick = () => {
printController.isPaused = true;
pauseBtn.style.display = 'none';
resumeBtn.style.display = 'inline-block';
progressBar.classList.add('paused');
addLogEntry('Print job paused by user', 'warning');
updateProgressBar(printController.currentLabel, quantity, '⏸️ Paused - Check printer paper');
};
resumeBtn.onclick = () => {
printController.isPaused = false;
resumeBtn.style.display = 'none';
pauseBtn.style.display = 'inline-block';
progressBar.classList.remove('paused');
addLogEntry('Print job resumed', 'success');
};
reprintBtn.onclick = async () => {
if (printController.lastPrintedLabel > 0) {
addLogEntry(`Reprinting label ${printController.lastPrintedLabel}...`, 'info');
try {
await generatePDFAndPrint(selectedPrinter, orderData, printController.lastPrintedLabel, quantity);
addLogEntry(`Label ${printController.lastPrintedLabel} reprinted successfully`, 'success');
} catch (error) {
addLogEntry(`Reprint failed: ${error.message}`, 'error');
}
}
};
cancelBtn.onclick = () => {
printController.isCancelled = true;
addLogEntry('Print job cancelled by user', 'error');
progressBar.classList.add('error');
updateProgressBar(printController.currentLabel, quantity, '❌ Cancelled');
};
try {
// Print each label sequentially
for (let i = 1; i <= quantity; i++) {
if (printController.isCancelled) {
addLogEntry('Print job terminated', 'error');
break;
}
// Wait while paused
while (printController.isPaused && !printController.isCancelled) {
await new Promise(resolve => setTimeout(resolve, 500));
}
if (printController.isCancelled) break;
printController.currentLabel = i;
updateProgressBar(i - 1, quantity, `Printing label ${i} of ${quantity}...`);
addLogEntry(`Sending label ${i} to printer...`, 'info');
try {
// Generate PDF and send to printer
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
printController.lastPrintedLabel = i;
updateProgressBar(i, quantity, `Label ${i} printed successfully`);
addLogEntry(`✓ Label ${i} printed successfully`, 'success');
// Show reprint button after each successful print
reprintBtn.style.display = 'inline-block';
} catch (printError) {
// Print error detected
progressBar.classList.add('error');
printController.failedLabels.push(i);
addLogEntry(`✗ Label ${i} failed: ${printError.message}`, 'error');
// Pause automatically on error
printController.isPaused = true;
pauseBtn.style.display = 'none';
resumeBtn.style.display = 'inline-block';
progressBar.classList.add('paused');
updateProgressBar(i - 1, quantity, '⚠️ ERROR - Check printer (paper jam/out of paper)');
// Wait for user to resume or cancel
while (printController.isPaused && !printController.isCancelled) {
await new Promise(resolve => setTimeout(resolve, 500));
}
if (!printController.isCancelled) {
// Retry failed label
addLogEntry(`Retrying label ${i}...`, 'warning');
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
addLogEntry(`✓ Label ${i} printed successfully (retry)`, 'success');
printController.lastPrintedLabel = i;
progressBar.classList.remove('error');
}
}
// Small delay between labels for printer processing
if (i < quantity && !printController.isCancelled) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
if (!printController.isCancelled) {
// All labels printed successfully
progressBar.classList.remove('paused', 'error');
updateProgressBar(quantity, quantity, '✅ All labels printed! Updating database...');
addLogEntry('All labels completed successfully', 'success');
// Hide control buttons
pauseBtn.style.display = 'none';
resumeBtn.style.display = 'none';
cancelBtn.style.display = 'none';
// 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');
addLogEntry('Database update failed', 'warning');
updateProgressBar(quantity, quantity, '⚠️ Labels printed but database update failed');
await new Promise(resolve => setTimeout(resolve, 2000));
} else {
addLogEntry('Database updated successfully', 'success');
updateProgressBar(quantity, quantity, '✅ Complete! Refreshing table...');
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (dbError) {
console.error('Database update error:', dbError);
addLogEntry(`Database error: ${dbError.message}`, 'error');
updateProgressBar(quantity, quantity, '⚠️ Labels printed but database update failed');
await new Promise(resolve => setTimeout(resolve, 2000));
}
// Close modal
modal.classList.remove('show');
// Show success notification
showNotification(`✅ Successfully printed ${quantity} labels!`, 'success');
// Refresh table to show updated status
document.getElementById('check-db-btn').click();
} else {
// Job was cancelled
await new Promise(resolve => setTimeout(resolve, 2000));
modal.classList.remove('show');
showNotification(`⚠️ Print job cancelled. ${printController.lastPrintedLabel} of ${quantity} labels printed.`, 'warning');
}
} catch (printError) {
modal.classList.remove('show');
throw new Error(`Print failed: ${printError.message}`);
}
} catch (error) {
modal.classList.remove('show');
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 qztrayInfo = document.getElementById('qztray-info');
const printButton = document.getElementById('print-label-btn');
if (printMethod === 'qztray') {
printerSelection.style.display = 'block';
qztrayInfo.style.display = 'block';
printButton.textContent = '🖨️ Print Labels Directly';
printButton.className = 'btn btn-primary';
} else {
printerSelection.style.display = 'none';
qztrayInfo.style.display = 'none';
printButton.textContent = '📄 Generate PDF Labels';
printButton.className = 'btn btn-success';
}
}
// Add event listeners for print method radio buttons
document.addEventListener('DOMContentLoaded', function() {
// 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);
});
</script>
{% endblock %}