1374 lines
61 KiB
Plaintext
Executable File
1374 lines
61 KiB
Plaintext
Executable File
{% 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;
|
||
}
|
||
</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>
|
||
|
||
<!-- 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>
|
||
<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;">×</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 from CDN');
|
||
const errorMsg = '❌ QZ Tray Library Not Loaded\n\n' +
|
||
'The QZ Tray JavaScript library failed to load from CDN.\n' +
|
||
'Check your internet connection and refresh the page.\n\n' +
|
||
'CDN: https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.js';
|
||
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');
|
||
|
||
// For our custom QZ Tray build with pairing key authentication,
|
||
// we DON'T need certificate/signature validation.
|
||
// We'll connect to the insecure WebSocket port (8181) and skip certificate setup entirely.
|
||
console.log('<27> Skipping certificate setup - using pairing key authentication instead');
|
||
|
||
// Configure QZ to NOT require certificates/signatures
|
||
// This tells QZ Tray to accept the connection without signature validation
|
||
// IMPORTANT: Do NOT set signature algorithm or signature promise
|
||
// This prevents the "Invalid function call: NONE" error
|
||
// Our custom QZ Tray build uses pairing keys instead
|
||
|
||
console.log('✅ Configured for pairing-key based authentication');
|
||
console.log('🔌 Attempting to connect to QZ Tray on client PC...');
|
||
console.log('📍 Connection info:', {
|
||
isActive: qz.websocket.isActive(),
|
||
defaultPorts: 'ws://localhost:8181 or wss://localhost:8182'
|
||
});
|
||
|
||
// Set connection timeout
|
||
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 CDN');
|
||
|
||
// Setup certificate handling - skip signature to avoid "Invalid function call: NONE"
|
||
// DO NOT set signature algorithm or promise - this causes the NONE error
|
||
console.log('✅ Configured for pairing-key authentication');
|
||
|
||
// 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 ws://localhost:8181...');
|
||
console.log('🔌 Connecting to WebSocket (ports 8181/8182)...');
|
||
|
||
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 internet connection\n';
|
||
errorMsg += '• Refresh the page (F5)\n';
|
||
errorMsg += '• Check if CDN is accessible:\n';
|
||
errorMsg += ' https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.js';
|
||
|
||
} 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...');
|
||
|
||
// Prepare data for PDF generation
|
||
const pdfData = {
|
||
...orderData,
|
||
quantity: 1, // Single label per print job
|
||
piece_number: pieceNumber,
|
||
total_pieces: totalPieces
|
||
};
|
||
|
||
|
||
await qz.print(config, data);
|
||
console.log('✅ PDF sent to printer successfully');
|
||
|
||
} 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
|
||
async function handleQZTrayPrint(selectedRow) {
|
||
try {
|
||
if (!qzTray) {
|
||
await initializeQZTray();
|
||
if (!qzTray) {
|
||
throw new Error('QZ Tray not available');
|
||
}
|
||
}
|
||
|
||
const selectedPrinter = document.getElementById('qztray-printer-select').value;
|
||
if (!selectedPrinter) {
|
||
showNotification('⚠️ Please select a printer first', 'warning');
|
||
return;
|
||
}
|
||
|
||
// Extract order data from row
|
||
const cells = selectedRow.querySelectorAll('td');
|
||
const orderData = {
|
||
id: cells[0].textContent,
|
||
comanda_productie: cells[1].textContent.trim(),
|
||
cod_articol: cells[2].textContent.trim(),
|
||
descr_com_prod: cells[3].textContent.trim(),
|
||
cantitate: parseInt(cells[4].textContent.trim()),
|
||
data_livrare: cells[5].textContent.trim(),
|
||
dimensiune: cells[6].textContent.trim(),
|
||
com_achiz_client: cells[7].textContent.trim(),
|
||
nr_linie_com_client: cells[8].textContent.trim(),
|
||
customer_name: cells[9].textContent.trim()
|
||
};
|
||
|
||
const quantity = orderData.cantitate;
|
||
console.log(`🖨️ Printing ${quantity} labels via QZ Tray to ${selectedPrinter}`);
|
||
|
||
const button = document.getElementById('print-label-btn');
|
||
const originalText = button.textContent;
|
||
button.textContent = `Printing 0/${quantity}...`;
|
||
button.disabled = true;
|
||
|
||
try {
|
||
// Print each label sequentially
|
||
for (let i = 1; i <= quantity; i++) {
|
||
button.textContent = `Printing ${i}/${quantity}...`;
|
||
|
||
// Generate PDF and send to printer
|
||
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
|
||
|
||
// Small delay between labels for printer processing
|
||
if (i < quantity) {
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
}
|
||
}
|
||
|
||
showNotification(`✅ Successfully printed ${quantity} labels!`, 'success');
|
||
|
||
// Mark order as printed and refresh table
|
||
setTimeout(() => {
|
||
document.getElementById('check-db-btn').click();
|
||
}, 1000);
|
||
|
||
} catch (printError) {
|
||
throw new Error(`Print failed: ${printError.message}`);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('QZ Tray print error:', error);
|
||
showNotification('❌ QZ Tray print error: ' + error.message, 'error');
|
||
} finally {
|
||
const button = document.getElementById('print-label-btn');
|
||
button.textContent = originalText;
|
||
button.disabled = false;
|
||
}
|
||
}
|
||
|
||
// Print Button Handler - Routes to appropriate print method
|
||
document.getElementById('print-label-btn').addEventListener('click', 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') {
|
||
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 %} |