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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user