Implement print labels module with PDF generation, QZ Tray integration, and theme support

- Migrate print_labels.html and print_lost_labels.html to standalone pages with header and theme toggle
- Implement dark/light theme support using data-theme attribute and CSS variables
- Add PDF generation endpoints for single and batch label printing
- Copy pdf_generator.py from original app with full label formatting (80mm x 105mm)
- Fix data response handling to correctly access data.orders from API endpoints
- Synchronize table text sizes across both print pages
- Remove help buttons from print pages
- Database column rename: data_livrara → data_livrare for consistency
- Update routes to use correct database column names
- Add 7 test orders to database (4 unprinted, 3 printed)
- Implement QZ Tray integration with PDF fallback for label printing
- All CSS uses theme variables for dark/light mode synchronization
This commit is contained in:
Quality App Developer
2026-02-04 23:57:51 +02:00
parent 572b5af570
commit e53e3acc8e
13 changed files with 6138 additions and 1822 deletions

View File

@@ -1,134 +1,70 @@
{% extends "base.html" %}
{% block head %}
<!-- Print Lost Labels CSS with theme support -->
<style>
/* Print Module Theme Support */
.scan-container {
background-color: var(--bg-primary);
color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease;
}
.card.search-card,
.card.scan-form-card,
.card.scan-table-card {
background-color: var(--bg-primary);
border-color: var(--border-color);
color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
.scan-table {
background-color: var(--bg-primary);
color: var(--text-primary);
}
.scan-table thead th {
background-color: var(--bg-secondary);
color: var(--text-primary);
border-color: var(--border-color);
}
.scan-table tbody td {
background-color: var(--bg-primary);
color: var(--text-primary);
border-color: var(--border-color);
}
.scan-table tbody tr:hover {
background-color: rgba(13, 110, 253, 0.08);
}
.form-control,
.form-control-sm,
select {
background-color: var(--input-bg);
color: var(--text-primary);
border-color: var(--input-border);
}
.form-control:focus,
.form-control-sm:focus,
select:focus {
background-color: var(--input-bg);
color: var(--text-primary);
border-color: var(--input-focus-border);
box-shadow: 0 0 0 3px var(--input-focus-shadow);
}
{# .floating-help-btn {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
}
.floating-help-btn a {
display: inline-block;
width: 50px;
height: 50px;
background-color: var(--bg-secondary);
color: var(--text-primary);
border-radius: 50%;
text-align: center;
line-height: 50px;
font-size: 24px;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.floating-help-btn a:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
} #}
/* Compact table styling for print_lost_labels page */
.print-lost-labels-compact .scan-table.print-module-table {
font-size: 10px;
}
.print-lost-labels-compact .scan-table.print-module-table thead th {
font-size: 10px;
padding: 6px 8px;
line-height: 1.2;
}
.print-lost-labels-compact .scan-table.print-module-table tbody td {
font-size: 9px;
padding: 4px 6px;
line-height: 1.3;
}
/* Keep important data slightly larger and bold */
.print-lost-labels-compact .scan-table.print-module-table tbody td:nth-child(2) {
font-size: 10px;
font-weight: 600;
}
/* Make numbers more compact */
.print-lost-labels-compact .scan-table.print-module-table tbody td:nth-child(5),
.print-lost-labels-compact .scan-table.print-module-table tbody td:nth-child(9),
.print-lost-labels-compact .scan-table.print-module-table tbody td:nth-child(13) {
font-size: 9px;
}
/* Reduce row height */
.print-lost-labels-compact .scan-table.print-module-table tbody tr {
height: auto;
min-height: 24px;
}
{% endblock %}
{% block content %}
<!-- Floating Help Button -->
{# <div class="floating-help-btn">
<a href="{{ url_for('labels.help', page='print_lost_labels') }}" target="_blank" title="Print Lost Labels Help">
📖
</a>
</div> #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Print Lost Labels - Quality App</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Base CSS from new app for header and theme styling -->
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/theme.css">
<!-- Print Module CSS from original app -->
<link rel="stylesheet" href="/static/css/print_module.css">
<!-- Libraries for barcode and PDF generation -->
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
</head>
<body>
<!-- Navigation Header from App -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<i class="fas fa-chart-bar"></i> Quality App v2
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="/labels/">
<i class="fas fa-arrow-left"></i> Back to Labels
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/">
<i class="fas fa-home"></i> Dashboard
</a>
</li>
<li class="nav-item">
<button id="themeToggleBtn" class="theme-toggle nav-link" type="button" title="Switch Theme">
<i class="fas fa-moon"></i>
</button>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user"></i> User Menu
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<li>
<a class="dropdown-item" href="/profile/">
<i class="fas fa-user-circle"></i> Profile
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="/logout/">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Print Lost Labels Module Content -->
<!-- ROW 1: Search Card (full width) -->
<div class="scan-container lost-labels print-lost-labels-compact">
<div class="card search-card">
@@ -150,10 +86,8 @@
<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 - Only visible for superadmin -->
{% if session.role == 'superadmin' %}
<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>
{% endif %}
<!-- Manage Keys Button - Only visible for admin/superadmin -->
<a href="/download-extension" class="btn btn-info btn-sm" target="_blank" style="font-size: 11px; padding: 4px 12px; display: none;" id="manage-keys-btn">🔑 Manage Keys</a>
</div>
<!-- Label Preview Section -->
<div id="label-preview" style="padding: 10px; position: relative; background: #fafafa; width: 301px; height: 434.7px;">
@@ -251,9 +185,9 @@
<small>(e.g., CP00000711-001, 002, ...)</small>
</div>
<!-- QZ Tray Installation Info - Simplified -->
<div id="qztray-info" style="width: 100%; margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef;">
<div style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 10px; text-align: center;">
<div style="font-size: 10px; color: #495057; margin-bottom: 8px;">
<div id="qztray-info" style="width: 100%; margin-top: 15px; padding-top: 15px; border-top: 1px solid var(--border-color);">
<div style="background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 6px; padding: 10px; text-align: center;">
<div style="font-size: 10px; color: var(--text-secondary); 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;">
@@ -299,15 +233,15 @@
<!-- JavaScript Libraries -->
<!-- JsBarcode library for real barcode generation -->
<script src="{{ url_for('static', filename='JsBarcode.all.min.js') }}"></script>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<!-- Add html2canvas library for capturing preview as image -->
<script src="{{ url_for('static', filename='html2canvas.min.js') }}"></script>
<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>
<script src="https://qz.glyphtree.com/api/latest/qz-tray.js"></script>
<script>
// Store all orders data for searching
const allOrders = {{ orders|tojson|safe }};
// Store all orders data for searching (will be populated by API fetch on page load)
let allOrders = [];
let selectedOrderData = null;
// QZ Tray Integration
@@ -536,8 +470,18 @@ async function loadQZTrayPrinters() {
// Print Button Handler
document.addEventListener('DOMContentLoaded', function() {
// Display last 20 printed orders on page load
displayRecentOrders(20);
// Fetch printed orders from API
fetch('/labels/api/printed-orders')
.then(response => response.json())
.then(data => {
allOrders = data.orders || [];
// Display last 20 printed orders on page load
displayRecentOrders(20);
})
.catch(error => {
console.error('Error fetching orders:', error);
alert('Error loading orders. Please refresh the page.');
});
setTimeout(initializeQZTray, 1000);
document.getElementById('print-label-btn').addEventListener('click', async function(e) {
@@ -666,7 +610,7 @@ async function generatePDFAndPrint(selectedPrinter, orderData, pieceNumber, tota
piece_number: pieceNumber,
total_pieces: totalPieces
};
const response = await fetch('/generate_label_pdf', {
const response = await fetch('/labels/api/generate-pdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(pdfData)
@@ -710,7 +654,7 @@ function handlePDFGeneration(selectedRow) {
const originalText = button.textContent;
button.textContent = 'Generating PDF...';
button.disabled = true;
fetch(`/generate_labels_pdf/${orderId}/true`, {
fetch(`/labels/api/generate-pdf/${orderId}/true`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
@@ -982,4 +926,50 @@ function printLabels() {
window.location.href = url;
}
</script>
{% endblock %}
<!-- Bootstrap JS for navbar functionality -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Theme toggle functionality -->
<script>
// Initialize theme from localStorage or default to light
function initializeTheme() {
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
document.body.className = savedTheme + '-mode';
updateThemeButton();
}
// Update theme button icon
function updateThemeButton() {
const themeToggleBtn = document.getElementById('themeToggleBtn');
if (themeToggleBtn) {
const currentTheme = document.documentElement.getAttribute('data-theme');
if (currentTheme === 'dark') {
themeToggleBtn.innerHTML = '<i class="fas fa-sun"></i>';
themeToggleBtn.title = 'Switch to Light Mode';
} else {
themeToggleBtn.innerHTML = '<i class="fas fa-moon"></i>';
themeToggleBtn.title = 'Switch to Dark Mode';
}
}
}
// Initialize theme on page load
initializeTheme();
// Theme toggle button handler (if it exists)
const themeToggleBtn = document.getElementById('themeToggleBtn');
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', function() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
document.body.className = newTheme + '-mode';
localStorage.setItem('theme', newTheme);
updateThemeButton();
});
}
</script>
</body>
</html>