Files
quality_recticel/backup/print_module.htmlold 3.bak
2025-10-05 14:32:47 -04:00

1844 lines
77 KiB
Plaintext
Executable File
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 %}
<!-- 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>
<!-- 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 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" 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 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: 15px;">
<button id="print-label-btn" class="btn btn-success" style="font-size: 14px; padding: 10px 30px; border-radius: 6px; font-weight: 600;">
<20> Print 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, CP00000711-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>
<!-- 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 (only show if multiple keys)
let selectedPairingKey = null;
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
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);
});
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() {
selectedPairingKey = this.value;
console.log(`🔑 Pairing key changed: ${this.options[this.selectedIndex].text}`);
});
// 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 = '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
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');
// Force display with inline style to override any CSS conflicts
modal.style.display = 'flex';
console.log('🔍 Modal classList after:', modal.classList.toString());
console.log('🔍 Modal computed display:', window.getComputedStyle(modal).display);
console.log('🔍 Modal inline display:', modal.style.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');
modal.style.display = 'none';
// 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');
modal.style.display = 'none';
showNotification(`⚠️ Print job cancelled. ${printController.lastPrintedLabel} of ${quantity} labels printed.`, 'warning');
}
} catch (printError) {
modal.classList.remove('show');
modal.style.display = 'none';
throw new Error(`Print failed: ${printError.message}`);
}
} catch (error) {
modal.classList.remove('show');
modal.style.display = 'none';
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';
}
}
// 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 %}