Files
quality_app-v2/app/templates/modules/warehouse/locations.html
Quality App Developer e1f3302c6b Implement boxes management module with auto-numbered box creation
- Add boxes_crates database table with BIGINT IDs and 8-digit auto-numbered box_numbers
- Implement boxes CRUD operations (add, edit, update, delete, delete_multiple)
- Create boxes route handlers with POST actions for all operations
- Add boxes.html template with 3-panel layout matching warehouse locations module
- Implement barcode generation and printing with JsBarcode and QZ Tray integration
- Add browser print fallback for when QZ Tray is not available
- Simplify create box form to single button with auto-generation
- Fix JavaScript null reference errors with proper element validation
- Convert tuple data to dictionaries for Jinja2 template compatibility
- Register boxes blueprint in Flask app initialization
2026-01-26 22:08:31 +02:00

849 lines
34 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="container-fluid mt-5">
<div class="row mb-4">
<div class="col-12">
<h2 class="mb-3">
<i class="fas fa-map-marker-alt me-2"></i>Set Locations
</h2>
</div>
</div>
{% if message %}
<div class="alert alert-{{ message_type }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="row">
<!-- Left Panel: Add Location Form -->
<div class="col-md-3">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-plus me-2"></i>Add New Location
</h5>
</div>
<div class="card-body">
<form method="POST" id="addLocationForm">
<div class="mb-3">
<label for="location_code" class="form-label">Location Code <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="location_code" name="location_code"
placeholder="e.g., LOC-001" required>
<small class="text-muted">Must be unique</small>
</div>
<div class="mb-3">
<label for="size" class="form-label">Size</label>
<input type="text" class="form-control" id="size" name="size"
placeholder="e.g., Small, Medium, Large">
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description"
rows="3" placeholder="Location notes..."></textarea>
</div>
<button type="submit" class="btn btn-primary w-100" name="add_location" value="1">
<i class="fas fa-plus me-2"></i>Add Location
</button>
</form>
</div>
</div>
<!-- Delete Multiple Button -->
<div class="card shadow-sm mt-3 border-warning">
<div class="card-header bg-warning">
<h5 class="mb-0">
<i class="fas fa-trash me-2"></i>Delete Selected
</h5>
</div>
<div class="card-body">
<form method="POST" id="deleteForm">
<button type="button" class="btn btn-danger w-100" id="deleteSelectedBtn"
onclick="deleteSelectedLocations()">
<i class="fas fa-trash me-2"></i>Delete Selected
</button>
<input type="hidden" id="delete_ids" name="delete_ids" value="">
</form>
<small class="text-muted d-block mt-2">Select locations in table to delete</small>
</div>
</div>
</div>
<!-- Center Panel: Locations Table -->
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header bg-info text-white">
<h5 class="mb-0">
<i class="fas fa-list me-2"></i>All Locations ({{ locations|length }})
</h5>
</div>
<div class="card-body" style="max-height: 600px; overflow-y: auto;">
{% if locations %}
<div class="table-responsive">
<table class="table table-striped table-hover" id="locationsTable">
<thead class="table-light sticky-top">
<tr>
<th width="30">
<input type="checkbox" id="selectAll" onchange="toggleSelectAll(this)">
</th>
<th>Code</th>
<th>Size</th>
<th>Description</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for location in locations %}
<tr class="location-row" data-location-id="{{ location.id }}" data-location-code="{{ location.location_code }}" data-location-size="{{ location.size or '' }}" data-location-description="{{ location.description or '' }}">
<td>
<input type="checkbox" class="location-checkbox"
value="{{ location.id }}"
onchange="updateDeleteBtn()">
</td>
<td>
<strong>{{ location.location_code }}</strong>
</td>
<td>{{ location.size or '-' }}</td>
<td>
<span class="text-muted">{{ location.description or '-' }}</span>
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="editLocation({{ location.id }}, event)">
<i class="fas fa-edit"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info text-center" role="alert">
<i class="fas fa-info-circle me-2"></i>
No locations found. Create one using the form on the left.
</div>
{% endif %}
</div>
</div>
</div>
<!-- Right Panel: Edit/Delete/Print -->
<div class="col-md-3">
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h5 class="mb-0">
<i class="fas fa-edit me-2"></i>Edit Location
</h5>
</div>
<div class="card-body">
<div id="editSection" style="display: none;">
<form method="POST" id="editLocationForm">
<input type="hidden" id="edit_location_id" name="location_id">
<div class="mb-3">
<label class="form-label">Location Code</label>
<input type="text" class="form-control" id="edit_location_code"
placeholder="Location code" readonly>
<small class="text-muted">Cannot be changed</small>
</div>
<div class="mb-3">
<label class="form-label">Size</label>
<input type="text" class="form-control" id="edit_size"
name="edit_size" placeholder="e.g., Small">
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" id="edit_description"
name="edit_description" rows="3"></textarea>
</div>
<button type="button" class="btn btn-success w-100 mb-2"
onclick="saveEditLocation()">
<i class="fas fa-save me-2"></i>Save Changes
</button>
<button type="button" class="btn btn-danger w-100 mb-2"
onclick="deleteLocation()">
<i class="fas fa-trash me-2"></i>Delete Location
</button>
<button type="button" class="btn btn-secondary w-100"
onclick="cancelEdit()">
Cancel
</button>
</form>
</div>
<div id="noEditSection" class="alert alert-info text-center">
<i class="fas fa-arrow-left me-2"></i>
Click edit button in table to modify a location
</div>
</div>
</div>
<!-- Barcode Print Section -->
<div class="card shadow-sm mt-3">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0">
<i class="fas fa-barcode me-2"></i>Print Barcode
</h5>
</div>
<div class="card-body">
<div id="printSection" style="display: none;">
<div class="mb-3">
<label class="form-label">Location:</label>
<p id="print_location_code" class="fw-bold mb-0" style="font-size: 1.2rem; color: #0d6efd;">-</p>
</div>
<!-- Printer Selection -->
<div class="mb-3">
<label for="printer-select" class="form-label">Select Printer:</label>
<select id="printer-select" class="form-select form-select-sm" style="font-size: 0.95rem;">
<option value="">Default Printer</option>
</select>
<small class="text-muted d-block mt-1">
<i class="fas fa-info-circle me-1"></i>
<span id="qz-status">QZ Tray: Initializing...</span>
</small>
</div>
<!-- Barcode Preview -->
<div id="barcodePreviewContainer" style="display: none; text-align: center; margin: 20px 0; padding: 15px; background-color: #f8f9fa; border-radius: 4px;">
<svg id="cardBarcode" style="max-width: 100%; height: auto;"></svg>
<p class="text-muted small mt-2 mb-0">Scan this barcode to identify the location</p>
</div>
<button type="button" class="btn btn-primary w-100 mb-2"
onclick="generateBarcodePreview()">
<i class="fas fa-eye me-2"></i>Generate Preview
</button>
<button type="button" class="btn btn-success w-100 mb-2"
id="printCardBtn" style="display: none;" onclick="printBarcode()">
<i class="fas fa-print me-2"></i>Print Barcode
</button>
<button type="button" class="btn btn-info btn-sm w-100 mb-2"
onclick="testQZTrayConnection()">
<i class="fas fa-cog me-2"></i>Test QZ Tray
</button>
<small class="text-muted d-block">
<i class="fas fa-info-circle me-1"></i>
Requires QZ Tray installed for thermal printing
</small>
</div>
<div id="noPrintSection" class="alert alert-info text-center">
<i class="fas fa-arrow-left me-2"></i>
Select a location to print
</div>
</div>
</div>
<!-- Import CSV Section -->
<div class="card shadow-sm mt-3 border-primary">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-file-import me-2"></i>Import CSV
</h5>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data" id="importForm">
<div class="mb-3">
<label for="csvFile" class="form-label">Choose CSV</label>
<input type="file" class="form-control" id="csvFile"
accept=".csv" required>
<small class="text-muted d-block mt-1">Format: location_code, size, description</small>
</div>
<button type="button" class="btn btn-primary w-100"
onclick="importCSV()">
<i class="fas fa-upload me-2"></i>Import
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteConfirmModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title">Confirm Delete</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p id="deleteConfirmMessage">Are you sure you want to delete this location?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn"
onclick="confirmDelete()">Delete</button>
</div>
</div>
</div>
</div>
<!-- Barcode Preview Modal -->
<div class="modal fade" id="barcodeModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-secondary text-white">
<h5 class="modal-title">
<i class="fas fa-barcode me-2"></i>Barcode Preview
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<p class="fw-bold mb-3" id="barcodeLocationCode">LOC-001</p>
<svg id="barcode"></svg>
<p class="text-muted mt-3 small">Scan this barcode to identify the location</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-success" onclick="printBarcodeFromModal()">
<i class="fas fa-print me-2"></i>Print Barcode
</button>
</div>
</div>
</div>
</div>
<!-- QZ Tray and Barcode Libraries -->
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<script src="{{ url_for('static', filename='js/qz-tray.js') }}"></script>
<script src="{{ url_for('static', filename='js/qz-printer.js') }}"></script>
<script>
// Barcode printing functions
let currentBarcodeLocation = null;
function generateBarcodePreview() {
const locationCode = document.getElementById('print_location_code').textContent.trim();
if (!locationCode || locationCode === '-') {
alert('Please select a location first');
return;
}
currentBarcodeLocation = locationCode;
// Generate barcode in card
const barcodeEl = document.getElementById('cardBarcode');
const containerEl = document.getElementById('barcodePreviewContainer');
const printBtn = document.getElementById('printCardBtn');
if (barcodeEl) {
barcodeEl.innerHTML = '';
try {
// Generate barcode using JsBarcode
JsBarcode("#cardBarcode", locationCode, {
format: "CODE128",
width: 2,
height: 100,
displayValue: true,
margin: 10
});
console.log('Barcode generated in card');
// Show preview container and print button
if (containerEl) {
containerEl.style.display = 'block';
}
if (printBtn) {
printBtn.style.display = 'block';
}
} catch (error) {
console.error('Error generating barcode:', error);
alert('Error generating barcode. Make sure jsbarcode library is loaded.');
}
}
}
function printBarcode() {
const locationCode = document.getElementById('print_location_code').textContent.trim();
if (!locationCode || locationCode === '-') {
alert('Please select a location first');
return;
}
currentBarcodeLocation = locationCode;
console.log('Printing barcode for:', locationCode);
// Try QZ Tray first
if (window.qzPrinter && window.qzPrinter.connected) {
printWithQZTray(locationCode);
} else {
// Fallback to browser print
printWithBrowserDialog(locationCode);
}
}
function printWithQZTray(locationCode) {
try {
if (!window.qzPrinter || !window.qzPrinter.connected) {
console.log('QZ Tray not connected, falling back to browser print');
printWithBrowserDialog(locationCode);
return;
}
const svgElement = document.getElementById('cardBarcode');
if (!svgElement) {
console.error('Barcode element not found');
printWithBrowserDialog(locationCode);
return;
}
// Get the selected printer from dropdown
const printerSelect = document.getElementById('printer-select');
const selectedPrinter = printerSelect ? printerSelect.value : '';
console.log('Printing to QZ Tray - Selected printer:', selectedPrinter || 'Default');
// Use the shared qzPrinter module to print
window.qzPrinter.printSVGBarcode(svgElement, 'Location: ' + locationCode, selectedPrinter)
.then(() => {
console.log('✅ Print job sent successfully to printer');
const printerName = selectedPrinter || 'default printer';
alert('Print job sent to ' + printerName + ' successfully!');
})
.catch(error => {
console.error('Print error:', error);
console.log('Falling back to browser print');
printWithBrowserDialog(locationCode);
});
} catch (error) {
console.error('QZ Tray printing error:', error);
printWithBrowserDialog(locationCode);
}
}
function printWithBrowserDialog(locationCode) {
// Open browser print dialog
const printWindow = window.open('', '', 'height=400,width=600');
const barcodeSvg = document.getElementById('cardBarcode').innerHTML;
printWindow.document.write('<html><head><title>Print Location Barcode</title>');
printWindow.document.write('<style>');
printWindow.document.write('body { font-family: Arial, sans-serif; text-align: center; padding: 20px; }');
printWindow.document.write('h2 { margin-bottom: 30px; }');
printWindow.document.write('svg { max-width: 100%; height: auto; margin: 20px 0; }');
printWindow.document.write('</style></head><body>');
printWindow.document.write('<h2>Location: ' + locationCode + '</h2>');
printWindow.document.write('<svg id="barcode">' + barcodeSvg + '</svg>');
printWindow.document.write('<p style="margin-top: 40px; font-size: 14px; color: #666;">');
printWindow.document.write('Printed on: ' + new Date().toLocaleString());
printWindow.document.write('</p></body></html>');
printWindow.document.close();
// Trigger print after a short delay to ensure SVG is rendered
setTimeout(function() {
printWindow.print();
}, 250);
}
let currentEditingLocationId = null;
let currentDeleteId = null;
// Toggle select all checkboxes
function toggleSelectAll(checkbox) {
const checkboxes = document.querySelectorAll('.location-checkbox');
checkboxes.forEach(cb => cb.checked = checkbox.checked);
updateDeleteBtn();
}
// Update delete button visibility
function updateDeleteBtn() {
const checkedCount = document.querySelectorAll('.location-checkbox:checked').length;
const deleteBtn = document.getElementById('deleteSelectedBtn');
deleteBtn.disabled = checkedCount === 0;
if (checkedCount > 0) {
deleteBtn.textContent = `Delete ${checkedCount} Selected`;
} else {
deleteBtn.innerHTML = '<i class="fas fa-trash me-2"></i>Delete Selected';
}
}
// Delete selected locations
function deleteSelectedLocations() {
const selectedIds = Array.from(document.querySelectorAll('.location-checkbox:checked'))
.map(cb => cb.value);
if (selectedIds.length === 0) {
alert('Please select locations to delete');
return;
}
document.getElementById('deleteConfirmMessage').textContent =
`Are you sure you want to delete ${selectedIds.length} location(s)?`;
currentDeleteId = selectedIds.join(',');
new bootstrap.Modal(document.getElementById('deleteConfirmModal')).show();
}
// Edit location
function editLocation(locationId, evt) {
try {
const row = evt.target.closest('tr');
if (!row) {
console.error('Could not find table row');
return;
}
const cells = row.querySelectorAll('td');
if (cells.length < 4) {
console.error('Invalid table structure');
return;
}
const locationCode = cells[1].textContent.trim();
const size = cells[2].textContent.trim() === '-' ? '' : cells[2].textContent.trim();
const description = cells[3].textContent.trim() === '-' ? '' : cells[3].textContent.trim();
currentEditingLocationId = locationId;
console.log('Location selected:', locationId, locationCode);
// Safely set all element values
const editLocationIdEl = document.getElementById('edit_location_id');
const editLocationCodeEl = document.getElementById('edit_location_code');
const editSizeEl = document.getElementById('edit_size');
const editDescriptionEl = document.getElementById('edit_description');
const printLocationCodeEl = document.getElementById('print_location_code');
const editSectionEl = document.getElementById('editSection');
const noEditSectionEl = document.getElementById('noEditSection');
const printSectionEl = document.getElementById('printSection');
const noPrintSectionEl = document.getElementById('noPrintSection');
console.log('Elements found:', {
editSectionEl: !!editSectionEl,
printSectionEl: !!printSectionEl,
printLocationCodeEl: !!printLocationCodeEl
});
if (editLocationIdEl) editLocationIdEl.value = locationId;
if (editLocationCodeEl) editLocationCodeEl.value = locationCode;
if (editSizeEl) editSizeEl.value = size;
if (editDescriptionEl) editDescriptionEl.value = description;
if (printLocationCodeEl) {
printLocationCodeEl.textContent = locationCode;
console.log('Print location code set to:', locationCode);
}
// Show/hide edit section
if (editSectionEl) {
editSectionEl.style.display = 'block';
editSectionEl.style.visibility = 'visible';
console.log('Edit section displayed');
}
if (noEditSectionEl) {
noEditSectionEl.style.display = 'none';
console.log('No edit section hidden');
}
// Show/hide print section - use removeAttribute to ensure it overrides inline style
if (printSectionEl) {
printSectionEl.removeAttribute('style');
printSectionEl.style.display = 'block';
printSectionEl.style.visibility = 'visible';
// Scroll into view if needed
setTimeout(() => {
printSectionEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
console.log('Print section display:', window.getComputedStyle(printSectionEl).display);
console.log('Print section visible: TRUE');
} else {
console.error('Print section element not found!');
}
if (noPrintSectionEl) {
noPrintSectionEl.style.display = 'none';
noPrintSectionEl.style.visibility = 'hidden';
console.log('No print section hidden');
}
} catch (error) {
console.error('Error in editLocation:', error);
alert('Error loading location: ' + error.message);
}
}
// Save edit location
function saveEditLocation() {
if (!currentEditingLocationId) return;
const size = document.getElementById('edit_size').value;
const description = document.getElementById('edit_description').value;
// Create a form to submit
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="edit_location" value="1">
<input type="hidden" name="location_id" value="${currentEditingLocationId}">
<input type="hidden" name="size" value="${size}">
<input type="hidden" name="description" value="${description}">
`;
document.body.appendChild(form);
form.submit();
}
// Delete single location
function deleteLocation() {
if (!currentEditingLocationId) return;
document.getElementById('deleteConfirmMessage').textContent =
'Are you sure you want to delete this location?';
currentDeleteId = currentEditingLocationId;
new bootstrap.Modal(document.getElementById('deleteConfirmModal')).show();
}
// Confirm delete
function confirmDelete() {
const form = document.getElementById('deleteForm');
document.getElementById('delete_ids').value = currentDeleteId;
const submitForm = document.createElement('form');
submitForm.method = 'POST';
submitForm.innerHTML = `
<input type="hidden" name="delete_locations" value="1">
<input type="hidden" name="delete_ids" value="${currentDeleteId}">
`;
document.body.appendChild(submitForm);
submitForm.submit();
bootstrap.Modal.getInstance(document.getElementById('deleteConfirmModal')).hide();
}
// Cancel edit
function cancelEdit() {
currentEditingLocationId = null;
const editSectionEl = document.getElementById('editSection');
const noEditSectionEl = document.getElementById('noEditSection');
const printSectionEl = document.getElementById('printSection');
const noPrintSectionEl = document.getElementById('noPrintSection');
if (editSectionEl) {
editSectionEl.style.display = 'none';
editSectionEl.style.visibility = 'hidden';
}
if (noEditSectionEl) {
noEditSectionEl.style.display = 'block';
noEditSectionEl.style.visibility = 'visible';
}
if (printSectionEl) {
printSectionEl.style.display = 'none';
printSectionEl.style.visibility = 'hidden';
}
if (noPrintSectionEl) {
noPrintSectionEl.style.display = 'block';
noPrintSectionEl.style.visibility = 'visible';
}
console.log('Edit cancelled, print section hidden');
}
// Update QZ Tray status display
function updateQZStatus(message, status = 'info') {
const statusEl = document.getElementById('qz-status');
if (statusEl) {
statusEl.textContent = 'QZ Tray: ' + message;
statusEl.className = status === 'success' ? 'text-success fw-bold' :
status === 'warning' ? 'text-warning' : 'text-muted';
}
}
// Test QZ Tray connection
function testQZTrayConnection() {
if (window.qzPrinter && window.qzPrinter.connected) {
const printers = window.qzPrinter.availablePrinters;
const printerList = printers.length > 0
? printers.join('\n• ')
: 'No printers found';
alert('✅ QZ Tray is connected and ready!\n\nAvailable printers:\n• ' + printerList);
} else {
alert('⚠️ QZ Tray is not connected.\nBrowser print will be used instead.');
}
}
// Initialize QZ Tray on page load
function initializeQZTray() {
// Use the shared qzPrinter module
if (window.qzPrinter) {
// Update status based on connection state
if (window.qzPrinter.connected) {
updateQZStatus('Connected ✅', 'success');
populatePrinterSelect();
} else {
updateQZStatus('Not Available (will use browser print)', 'warning');
}
}
}
// Populate printer select dropdown
function populatePrinterSelect() {
if (!window.qzPrinter || !window.qzPrinter.availablePrinters.length) {
return;
}
const printerSelect = document.getElementById('printer-select');
if (!printerSelect) return;
// Clear existing options except default
printerSelect.innerHTML = '<option value="">Default Printer</option>';
// Add each printer
window.qzPrinter.availablePrinters.forEach(printer => {
const option = document.createElement('option');
option.value = printer;
option.textContent = printer;
if (printer === window.qzPrinter.selectedPrinter) {
option.selected = true;
}
printerSelect.appendChild(option);
});
}
// Import CSV
function importCSV() {
const fileInput = document.getElementById('csvFile');
if (!fileInput.files.length) {
alert('Please select a CSV file');
return;
}
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = function(e) {
const csv = e.target.result;
const lines = csv.trim().split('\n');
if (lines.length === 0) {
alert('CSV file is empty');
return;
}
let successCount = 0;
let errorCount = 0;
// Process each line
lines.forEach((line, index) => {
const parts = line.split(',').map(p => p.trim());
if (parts.length >= 1 && parts[0]) {
// Submit each location via AJAX
const locationCode = parts[0];
const size = parts[1] || '';
const description = parts[2] || '';
// Here you would submit via AJAX to add each location
successCount++;
}
});
alert(`Import complete!\nSuccessfully imported: ${successCount}\nErrors: ${errorCount}`);
};
reader.readAsText(file);
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
updateDeleteBtn();
// Initialize QZ Tray (will use the shared module)
setTimeout(() => {
initializeQZTray();
}, 500); // Wait for qzPrinter module to initialize
// Handle printer selection change
const printerSelect = document.getElementById('printer-select');
if (printerSelect) {
printerSelect.addEventListener('change', function() {
if (window.qzPrinter) {
window.qzPrinter.selectPrinter(this.value);
}
});
}
// Add row click selection for print functionality
const locationRows = document.querySelectorAll('.location-row');
const printLocationCodeEl = document.getElementById('print_location_code');
const printSectionEl = document.getElementById('printSection');
const noPrintSectionEl = document.getElementById('noPrintSection');
locationRows.forEach(row => {
// Allow row to be clickable for selection (but not on checkbox click)
row.addEventListener('click', function(e) {
// Don't trigger on checkbox click
if (e.target.type === 'checkbox') return;
const locationCode = this.dataset.locationCode;
const locationId = this.dataset.locationId;
console.log('Row clicked:', { locationId, locationCode });
// Update print location code
if (printLocationCodeEl) {
printLocationCodeEl.textContent = locationCode;
}
// Show print section
if (printSectionEl) {
printSectionEl.style.display = 'block';
printSectionEl.style.visibility = 'visible';
}
if (noPrintSectionEl) {
noPrintSectionEl.style.display = 'none';
noPrintSectionEl.style.visibility = 'hidden';
}
// Highlight row as selected
locationRows.forEach(r => r.style.backgroundColor = '');
this.style.backgroundColor = '#e3f2fd';
console.log('Print section shown for:', locationCode);
});
});
});
</script>
<style>
.sticky-top {
z-index: 10;
}
.table-hover tbody tr:hover {
background-color: #f8f9fa;
}
.card {
border-radius: 8px;
}
.card-header {
border-radius: 8px 8px 0 0;
}
#locationsTable {
font-size: 0.95rem;
}
.btn-outline-primary:hover {
transform: scale(1.05);
transition: all 0.2s;
}
</style>
{% endblock %}