Initial commit: Quality App v2 - FG Scan Module with Reports

This commit is contained in:
Quality App Developer
2026-01-25 22:25:18 +02:00
commit 3c5a273a89
66 changed files with 15368 additions and 0 deletions

View File

@@ -0,0 +1,910 @@
{% extends "base.html" %}
{% block title %}FG Scan - Quality App{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/fg_scan.css') }}">
<script src="https://cdn.jsdelivr.net/npm/qz-tray@2.1.0/qz-tray.js"></script>
{% endblock %}
{% block content %}
<div class="scan-container">
<!-- Form Card -->
<div class="scan-form-card">
<h3 class="form-title">FG Scan Entry</h3>
<form id="scanForm" method="POST" class="scan-form">
<label for="operator_code">Operator Code:</label>
<input type="text" id="operator_code" name="operator_code" required>
<div class="error-message" id="error_operator_code"></div>
<label for="cp_code">CP Code:</label>
<input type="text" id="cp_code" name="cp_code" required>
<div class="error-message" id="error_cp_code"></div>
<label for="oc1_code">OC1 Code:</label>
<input type="text" id="oc1_code" name="oc1_code">
<div class="error-message" id="error_oc1_code"></div>
<label for="oc2_code">OC2 Code:</label>
<input type="text" id="oc2_code" name="oc2_code">
<div class="error-message" id="error_oc2_code"></div>
<label for="defect_code">Defect Code:</label>
<input type="text" id="defect_code" name="defect_code" maxlength="3">
<div class="error-message" id="error_defect_code"></div>
<label for="date_time">Date/Time:</label>
<input type="text" id="date_time" name="date_time" readonly>
<div class="form-buttons">
<button type="submit" class="btn-submit">Submit Scan</button>
<button type="button" class="btn-clear" id="clearOperator">Clear Quality Operator</button>
</div>
<div class="form-options">
<label class="checkbox-label">
<input type="checkbox" id="scanToBoxes" name="scan_to_boxes">
Scan To Boxes
</label>
</div>
<div id="quickBoxSection" style="display: none;" class="quick-box-section">
<button type="button" class="btn-secondary" id="quickBoxLabel">Quick Box Label Creation</button>
</div>
</form>
</div>
<!-- Latest Scans Table -->
<div class="scan-table-card">
<h3 class="table-title">Latest Scans</h3>
<div class="table-wrapper">
<table class="scan-table">
<thead>
<tr>
<th>ID</th>
<th>Op Code</th>
<th>CP Code</th>
<th>OC1</th>
<th>OC2</th>
<th>Defect Code</th>
<th>Date</th>
<th>Time</th>
<th>Approved Qty</th>
<th>Rejected Qty</th>
</tr>
</thead>
<tbody id="scansTableBody">
{% if scan_groups %}
{% for scan_group in scan_groups %}
<tr>
<td>{{ scan_group.id }}</td>
<td>{{ scan_group.operator_code }}</td>
<td>{{ scan_group.cp_code }}</td>
<td>{{ scan_group.oc1_code or '-' }}</td>
<td>{{ scan_group.oc2_code or '-' }}</td>
<td>{{ scan_group.defect_code or '-' }}</td>
<td>{{ scan_group.date }}</td>
<td>{{ scan_group.time }}</td>
<td>{{ scan_group.approved_qty }}</td>
<td>{{ scan_group.rejected_qty }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="10" style="text-align: center; padding: 20px; color: var(--text-secondary);">
<i class="fas fa-inbox"></i> No scans recorded yet. Submit a scan to see results here.
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Box Assignment Modal -->
<div id="boxAssignmentModal" class="box-modal" style="display: none;">
<div class="box-modal-content">
<div class="modal-header">
<h2>Assign to Box</h2>
<button type="button" class="modal-close" id="closeModal">&times;</button>
</div>
<div class="modal-body">
<label for="boxNumber">Box Number:</label>
<input type="text" id="boxNumber" placeholder="Enter box number">
<label for="boxQty">Quantity:</label>
<input type="number" id="boxQty" placeholder="Enter quantity" min="1">
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary" id="cancelModal">Cancel</button>
<button type="button" class="btn-submit" id="assignToBox">Assign</button>
</div>
</div>
</div>
<script>
// Global variables
let scanToBoxesEnabled = false;
let currentCpCode = '';
let qzTrayReady = false;
let cpCodeLastInputTime = null;
// Get form input references FIRST (before using them) - with null safety check
const operatorCodeInput = document.getElementById('operator_code');
const cpCodeInput = document.getElementById('cp_code');
const oc1CodeInput = document.getElementById('oc1_code');
const oc2CodeInput = document.getElementById('oc2_code');
const defectCodeInput = document.getElementById('defect_code');
// Safety check - ensure all form inputs are available
if (!operatorCodeInput || !cpCodeInput || !oc1CodeInput || !oc2CodeInput || !defectCodeInput) {
console.error('❌ Error: Required form inputs not found in DOM');
console.log('operatorCodeInput:', operatorCodeInput);
console.log('cpCodeInput:', cpCodeInput);
console.log('oc1CodeInput:', oc1CodeInput);
console.log('oc2CodeInput:', oc2CodeInput);
console.log('defectCodeInput:', defectCodeInput);
}
// Initialize QZ Tray only when needed (lazy loading)
// Don't connect on page load - only connect when user enables "Scan To Boxes"
function initializeQzTray() {
if (typeof qz === 'undefined') {
console.log(' QZ Tray library not loaded');
return false;
}
try {
qz.security.setSignaturePromise(function(toSign) {
return new Promise(function(resolve, reject) {
// For development, we'll allow unsigned requests
resolve();
});
});
qz.websocket.connect().then(function() {
qzTrayReady = true;
console.log('✅ QZ Tray connected successfully');
}).catch(function(err) {
console.log(' QZ Tray connection failed:', err);
qzTrayReady = false;
});
return true;
} catch(err) {
console.log(' QZ Tray initialization error:', err);
return false;
}
}
// Update date/time display
function updateDateTime() {
const now = new Date();
const dateStr = now.toLocaleDateString('en-US');
const timeStr = now.toLocaleTimeString('en-US', { hour12: true });
const dateTimeInput = document.getElementById('date_time');
if (dateTimeInput) {
dateTimeInput.value = dateStr + ' ' + timeStr;
}
}
updateDateTime();
setInterval(updateDateTime, 1000);
// Load operator code from localStorage
function loadOperatorCode() {
if (!operatorCodeInput) return;
const saved = localStorage.getItem('quality_operator_code');
if (saved) {
operatorCodeInput.value = saved;
}
}
// Check if we need to clear fields after a successful submission
const shouldClearAfterSubmit = localStorage.getItem('fg_scan_clear_after_submit');
if (shouldClearAfterSubmit === 'true' && cpCodeInput && oc1CodeInput && oc2CodeInput && defectCodeInput) {
// Clear the flag
localStorage.removeItem('fg_scan_clear_after_submit');
localStorage.removeItem('fg_scan_last_cp');
localStorage.removeItem('fg_scan_last_defect');
// Clear CP code, OC1, OC2, and defect code for next scan (NOT operator code)
cpCodeInput.value = '';
oc1CodeInput.value = '';
oc2CodeInput.value = '';
defectCodeInput.value = '';
// Show success indicator
setTimeout(function() {
// Focus on CP code field for next scan
if (cpCodeInput) {
cpCodeInput.focus();
}
// Add visual feedback
const successIndicator = document.createElement('div');
successIndicator.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4CAF50;
color: white;
padding: 10px 20px;
border-radius: 5px;
z-index: 1000;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
font-weight: bold;
`;
successIndicator.textContent = '✅ Scan recorded! Ready for next scan';
document.body.appendChild(successIndicator);
// Remove success indicator after 3 seconds
setTimeout(function() {
if (successIndicator.parentNode) {
successIndicator.parentNode.removeChild(successIndicator);
}
}, 3000);
}, 100);
}
// Focus on the first empty required field (only if not clearing after submit)
if (shouldClearAfterSubmit !== 'true' && operatorCodeInput && cpCodeInput && oc1CodeInput && oc2CodeInput && defectCodeInput) {
if (!operatorCodeInput.value) {
operatorCodeInput.focus();
} else if (!cpCodeInput.value) {
cpCodeInput.focus();
} else if (!oc1CodeInput.value) {
oc1CodeInput.focus();
} else if (!oc2CodeInput.value) {
oc2CodeInput.focus();
} else {
defectCodeInput.focus();
}
}
loadOperatorCode();
// Create error message elements
const operatorErrorMessage = document.createElement('div');
operatorErrorMessage.className = 'error-message';
operatorErrorMessage.id = 'operator-error';
operatorErrorMessage.textContent = 'Operator code must start with OP and be 4 characters';
operatorCodeInput.parentNode.insertBefore(operatorErrorMessage, operatorCodeInput.nextSibling);
const cpErrorMessage = document.createElement('div');
cpErrorMessage.className = 'error-message';
cpErrorMessage.id = 'cp-error';
cpErrorMessage.textContent = 'CP code must start with CP and be 15 characters';
cpCodeInput.parentNode.insertBefore(cpErrorMessage, cpCodeInput.nextSibling);
const oc1ErrorMessage = document.createElement('div');
oc1ErrorMessage.className = 'error-message';
oc1ErrorMessage.id = 'oc1-error';
oc1ErrorMessage.textContent = 'OC1 code must start with OC and be 4 characters';
oc1CodeInput.parentNode.insertBefore(oc1ErrorMessage, oc1CodeInput.nextSibling);
const oc2ErrorMessage = document.createElement('div');
oc2ErrorMessage.className = 'error-message';
oc2ErrorMessage.id = 'oc2-error';
oc2ErrorMessage.textContent = 'OC2 code must start with OC and be 4 characters';
oc2CodeInput.parentNode.insertBefore(oc2ErrorMessage, oc2CodeInput.nextSibling);
const defectErrorMessage = document.createElement('div');
defectErrorMessage.className = 'error-message';
defectErrorMessage.id = 'defect-error';
defectErrorMessage.textContent = 'Defect code must be a 3-digit number (e.g., 000, 001, 123)';
defectCodeInput.parentNode.insertBefore(defectErrorMessage, defectCodeInput.nextSibling);
// ===== CP CODE AUTO-COMPLETE LOGIC =====
let cpCodeAutoCompleteTimeout = null;
function autoCompleteCpCode() {
const value = cpCodeInput.value.trim().toUpperCase();
// Only process if it starts with "CP" but is not 15 characters
if (value.startsWith('CP') && value.length < 15 && value.length > 2) {
console.log('Auto-completing CP code:', value);
// Check if there's a hyphen in the value
if (value.includes('-')) {
// Split by hyphen: CP[base]-[suffix]
const parts = value.split('-');
if (parts.length === 2) {
const cpPrefix = parts[0]; // e.g., "CP00002042"
const suffix = parts[1]; // e.g., "1" or "12" or "123" or "3"
console.log('CP prefix:', cpPrefix, 'Suffix:', suffix);
// Always pad the suffix to exactly 4 digits
const paddedSuffix = suffix.padStart(4, '0');
// Construct the complete CP code
const completedCpCode = `${cpPrefix}-${paddedSuffix}`;
console.log('Completed CP code length:', completedCpCode.length, 'Code:', completedCpCode);
// Ensure it's exactly 15 characters
if (completedCpCode.length === 15) {
console.log('✅ Completed CP code:', completedCpCode);
cpCodeInput.value = completedCpCode;
// Show visual feedback
cpCodeInput.style.backgroundColor = '#e8f5e9';
setTimeout(() => {
cpCodeInput.style.backgroundColor = '';
}, 500);
// Move focus to next field (OC1 code)
setTimeout(() => {
oc1CodeInput.focus();
console.log('✅ Auto-completed CP Code and advanced to OC1');
}, 50);
// Show completion notification
showNotification(`✅ CP Code auto-completed: ${completedCpCode}`, 'success');
} else {
console.log('⚠️ Completed code length is not 15:', completedCpCode.length);
}
}
} else {
console.log('⏳ Waiting for hyphen to be entered before auto-completing');
}
} else {
if (value.length >= 15) {
console.log(' CP code is already complete (15 characters)');
}
}
}
cpCodeInput.addEventListener('input', function() {
cpCodeLastInputTime = Date.now();
const currentValue = this.value.trim().toUpperCase();
this.value = currentValue; // Convert to uppercase
// Clear existing timeout
if (cpCodeAutoCompleteTimeout) {
clearTimeout(cpCodeAutoCompleteTimeout);
}
console.log('CP Code input changed:', currentValue);
// Validate CP code prefix
if (currentValue.length >= 2 && !currentValue.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
} else {
cpErrorMessage.classList.remove('show');
this.setCustomValidity('');
// Auto-advance when field is complete and valid
if (currentValue.length === 15 && currentValue.startsWith('CP')) {
setTimeout(() => {
oc1CodeInput.focus();
console.log('✅ Auto-advanced from CP Code to OC1 Code');
}, 50);
}
}
// If hyphen is present and value is less than 15 chars, process immediately
if (currentValue.includes('-') && currentValue.length < 15) {
console.log('Hyphen detected, checking for auto-complete');
cpCodeAutoCompleteTimeout = setTimeout(() => {
console.log('Processing auto-complete after hyphen');
autoCompleteCpCode();
}, 500);
} else if (currentValue.length < 15 && currentValue.startsWith('CP')) {
// Set normal 2-second timeout only when no hyphen yet
cpCodeAutoCompleteTimeout = setTimeout(() => {
console.log('2-second timeout triggered for CP code');
autoCompleteCpCode();
}, 2000);
}
});
// Also trigger auto-complete when focus leaves the field (blur event)
cpCodeInput.addEventListener('blur', function() {
console.log('CP Code blur event triggered with value:', this.value);
if (cpCodeAutoCompleteTimeout) {
clearTimeout(cpCodeAutoCompleteTimeout);
}
autoCompleteCpCode();
});
// Prevent leaving CP code field if invalid
cpCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
// Return focus to this field
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if CP code is invalid
cpCodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
this.select();
}
});
// Prevent focusing on CP code if operator code is invalid
cpCodeInput.addEventListener('focus', function(e) {
if (operatorCodeInput.value.length > 0 && !operatorCodeInput.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
operatorCodeInput.focus();
operatorCodeInput.select();
}
});
// ===== OPERATOR CODE VALIDATION =====
operatorCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
} else {
operatorErrorMessage.classList.remove('show');
this.setCustomValidity('');
// Auto-advance when field is complete and valid
if (value.length === 4 && value.startsWith('OP')) {
setTimeout(() => {
cpCodeInput.focus();
console.log('✅ Auto-advanced from Operator Code to CP Code');
}, 50);
}
}
});
operatorCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
operatorCodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
this.select();
}
});
// ===== OC1 CODE VALIDATION =====
oc1CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc1ErrorMessage.classList.remove('show');
this.setCustomValidity('');
// Auto-advance when field is complete and valid
if (value.length === 4 && value.startsWith('OC')) {
setTimeout(() => {
oc2CodeInput.focus();
console.log('✅ Auto-advanced from OC1 Code to OC2 Code');
}, 50);
}
}
});
oc1CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
oc1CodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
this.select();
}
});
oc1CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
});
// ===== OC2 CODE VALIDATION =====
oc2CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc2ErrorMessage.classList.remove('show');
this.setCustomValidity('');
// Auto-advance when field is complete and valid
if (value.length === 4 && value.startsWith('OC')) {
setTimeout(() => {
defectCodeInput.focus();
console.log('✅ Auto-advanced from OC2 Code to Defect Code');
}, 50);
}
}
});
oc2CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
oc2CodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
this.select();
}
});
oc2CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
});
// ===== DEFECT CODE VALIDATION =====
defectCodeInput.addEventListener('input', function() {
// Remove any non-digit characters
this.value = this.value.replace(/\D/g, '');
// Validate if it's a valid 3-digit number when length is 3
if (this.value.length === 3) {
const isValid = /^\d{3}$/.test(this.value);
if (!isValid) {
defectErrorMessage.classList.add('show');
this.setCustomValidity('Must be a 3-digit number');
} else {
defectErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
} else {
defectErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
// Auto-submit when 3 characters are entered and all validations pass
if (this.value.length === 3) {
// Validate operator code before submitting
if (!operatorCodeInput.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
operatorCodeInput.focus();
operatorCodeInput.setCustomValidity('Must start with OP');
return;
}
// Validate CP code before submitting
if (!cpCodeInput.value.startsWith('CP') || cpCodeInput.value.length !== 15) {
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.setCustomValidity('Must start with CP and be complete');
return;
}
// Validate OC1 code before submitting
if (!oc1CodeInput.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.setCustomValidity('Must start with OC');
return;
}
// Validate OC2 code before submitting
if (!oc2CodeInput.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.setCustomValidity('Must start with OC');
return;
}
// Validate defect code is a valid 3-digit number
const isValidDefectCode = /^\d{3}$/.test(this.value);
if (!isValidDefectCode) {
defectErrorMessage.classList.add('show');
this.focus();
this.setCustomValidity('Must be a 3-digit number');
return;
}
// Clear all custom validity states before submitting
operatorCodeInput.setCustomValidity('');
cpCodeInput.setCustomValidity('');
oc1CodeInput.setCustomValidity('');
oc2CodeInput.setCustomValidity('');
this.setCustomValidity('');
// ===== TIME FIELD AUTO-UPDATE (CRITICAL) =====
// Update time field to current time before submitting
const timeInput = document.getElementById('date_time');
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const timeValue = `${hours}:${minutes}:${seconds}`;
// Parse the current datetime display and update just the time part
const dateStr = timeInput.value.split(' ').slice(0, -1).join(' '); // Get date part
timeInput.value = dateStr + ' ' + timeValue;
console.log('✅ Time field updated to:', timeValue);
// Save current scan data to localStorage for clearing after reload
localStorage.setItem('fg_scan_clear_after_submit', 'true');
localStorage.setItem('fg_scan_last_cp', cpCodeInput.value);
localStorage.setItem('fg_scan_last_defect', defectCodeInput.value);
// Auto-submit the form
console.log('Auto-submitting form on 3-digit defect code');
document.getElementById('scanForm').submit();
}
});
defectCodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
if (oc2CodeInput.value.length > 0 && !oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.select();
}
});
// ===== CLEAR OPERATOR CODE BUTTON =====
document.getElementById('clearOperator').addEventListener('click', function(e) {
e.preventDefault();
operatorCodeInput.value = '';
localStorage.removeItem('quality_operator_code');
operatorCodeInput.focus();
showNotification('Quality Operator Code cleared', 'info');
});
// ===== SAVE OPERATOR CODE ON INPUT =====
operatorCodeInput.addEventListener('input', function() {
if (this.value.startsWith('OP') && this.value.length >= 3) {
localStorage.setItem('quality_operator_code', this.value);
}
});
// Form submission
document.getElementById('scanForm').addEventListener('submit', function(e) {
e.preventDefault();
if (!validateForm()) {
return;
}
// Save operator code
localStorage.setItem('quality_operator_code', document.getElementById('operator_code').value.trim());
const formData = new FormData(this);
// If AJAX is needed (scan to boxes)
if (scanToBoxesEnabled) {
fetch('{{ url_for("quality.fg_scan") }}', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('Scan saved successfully!', 'success');
resetForm();
document.getElementById('boxAssignmentModal').style.display = 'flex';
} else {
showNotification('Error saving scan', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Error saving scan', 'error');
});
} else {
// Regular form submission
this.submit();
}
});
// Scan to boxes functionality
document.getElementById('scanToBoxes').addEventListener('change', function() {
scanToBoxesEnabled = this.checked;
document.getElementById('quickBoxSection').style.display = this.checked ? 'block' : 'none';
if (this.checked) {
// Initialize QZ Tray when user enables the feature
console.log('Scan To Boxes enabled - initializing QZ Tray...');
initializeQzTray();
}
});
// Quick box label creation
document.getElementById('quickBoxLabel').addEventListener('click', function() {
if (!qzTrayReady) {
alert('QZ Tray is not connected. Please ensure QZ Tray is running.');
return;
}
const cpCode = document.getElementById('cp_code').value.trim();
if (!cpCode) {
alert('Please enter a CP code first');
return;
}
// Create label configuration for QZ Tray
const label = {
type: 'label',
cpCode: cpCode,
createdAt: new Date().toISOString()
};
// Send to printer via QZ Tray
qz.print({
type: 'label',
format: cpCode
}).catch(function(err) {
console.error('Print error:', err);
alert('Error printing label');
});
});
// Modal functionality
document.getElementById('closeModal').addEventListener('click', function() {
document.getElementById('boxAssignmentModal').style.display = 'none';
});
document.getElementById('cancelModal').addEventListener('click', function() {
document.getElementById('boxAssignmentModal').style.display = 'none';
});
document.getElementById('assignToBox').addEventListener('click', function() {
const boxNumber = document.getElementById('boxNumber').value.trim();
const boxQty = document.getElementById('boxQty').value.trim();
if (!boxNumber) {
alert('Please enter a box number');
return;
}
if (!boxQty || isNaN(boxQty) || parseInt(boxQty) < 1) {
alert('Please enter a valid quantity');
return;
}
// Submit box assignment
const data = {
box_number: boxNumber,
quantity: boxQty,
cp_code: currentCpCode
};
fetch('{{ url_for("quality.fg_scan") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('Box assigned successfully!', 'success');
document.getElementById('boxAssignmentModal').style.display = 'none';
document.getElementById('boxNumber').value = '';
document.getElementById('boxQty').value = '';
}
})
.catch(error => console.error('Error:', error));
});
// Utility functions
function resetForm() {
document.getElementById('cp_code').value = '';
document.getElementById('oc1_code').value = '';
document.getElementById('oc2_code').value = '';
document.getElementById('defect_code').value = '';
currentCpCode = '';
document.getElementById('cp_code').focus();
}
function showNotification(message, type) {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
notification.style.opacity = '1';
document.body.appendChild(notification);
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Dark mode support
function applyDarkModeStyles() {
const isDarkMode = document.body.classList.contains('dark-mode');
if (isDarkMode) {
document.documentElement.style.setProperty('--bg-color', '#1e1e1e');
document.documentElement.style.setProperty('--text-color', '#e0e0e0');
}
}
// Check dark mode on page load
if (document.body.classList.contains('dark-mode')) {
applyDarkModeStyles();
}
// Listen for dark mode changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
applyDarkModeStyles();
}
});
});
observer.observe(document.body, { attributes: true });
</script>
{% endblock %}