4 Commits

Author SHA1 Message Date
Javier
b97424554c feat: add column headers to scan list 2026-02-11 10:04:39 -06:00
Javier
de72a1fb9e feat: silent success scan, error beep on fail and duplicate 2026-02-11 09:50:34 -06:00
Javier
caeefa5d61 bump: version 0.18.1 2026-02-11 09:25:02 -06:00
Javier
ea403934d3 feat: remove page reloads on scan, use DOM updates for speed 2026-02-11 09:24:44 -06:00
4 changed files with 65 additions and 19 deletions

2
app.py
View File

@@ -28,7 +28,7 @@ app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)
# 1. Define the version
APP_VERSION = '0.18.0'
APP_VERSION = '0.18.1'
# 2. Inject it into all templates automatically
@app.context_processor

View File

@@ -116,9 +116,9 @@ def execute_pipeline(actions, barcode, context):
placeholders = ', '.join(['?'] * len(cols))
sql = f"INSERT INTO {context['table_name']} ({', '.join(cols)}) VALUES ({placeholders})"
execute_db(sql, vals)
new_id = execute_db(sql, vals)
return {'success': True, 'message': 'Saved Successfully', 'data': field_values}
return {'success': True, 'message': 'Saved Successfully', 'detail_id': new_id, 'data': field_values}
except Exception as e:
return {'success': False, 'message': f"Database Error: {str(e)}", 'data': field_values}
else:

View File

@@ -109,6 +109,12 @@
<i class="fa-solid fa-file-import"></i> Bulk Import Excel
</button>
</div>
<div class="scans-grid scan-header-row" style="--field-count: {{ detail_fields|length }};">
{% for field in detail_fields %}
<div class="scan-row-cell scan-col-header">{{ field.field_label }}</div>
{% endfor %}
<div class="scan-row-cell scan-col-header">Status</div>
</div>
<div id="scansList" class="scans-grid" style="--field-count: {{ detail_fields|length }};">
{% for scan in scans %}
<div class="scan-row scan-row-{{ scan.duplicate_status }}"
@@ -247,6 +253,7 @@ function processSmartScan(barcode, confirm = false) {
// --- 1. HANDLE DUPLICATE CONFIRMATION ---
if (data.needs_confirmation) {
playErrorBeep();
isSmartScan = true;
currentDupKeyValue = barcode;
if (data.duplicate_status === 'dup_same_session') {
@@ -296,23 +303,23 @@ function processSmartScan(barcode, confirm = false) {
}
// --- 3. STANDARD SUCCESS/FAIL ---
if (data.success) {
if (data.detail_id && data.data) {
addScanToList(data.detail_id, data.data, data.data.duplicate_status);
}
feedbackArea.style.display = 'none';
if(smartInput) {
smartInput.value = '';
smartInput.focus();
}
feedbackArea.style.display = 'block';
if (data.success) {
feedbackArea.style.background = 'rgba(40, 167, 69, 0.2)';
feedbackArea.style.border = '1px solid #28a745';
feedbackText.style.color = '#28a745';
feedbackText.textContent = data.message;
if (data.message.includes('Saved')) setTimeout(() => location.reload(), 800);
} else {
playErrorBeep();
feedbackArea.style.display = 'block';
feedbackArea.style.background = 'rgba(220, 53, 69, 0.2)';
feedbackArea.style.border = '1px solid #dc3545';
feedbackText.style.color = '#dc3545';
feedbackText.textContent = data.message;
if(smartInput) smartInput.focus();
}
})
.catch(err => {
@@ -320,7 +327,7 @@ function processSmartScan(barcode, confirm = false) {
console.error(err); // Log it, don't popup alert to annoy user
});
}
// Function to handle the "Save" click from the Details Modal
// Function to handle the "Save" click from the Details Modal
function saveSmartScanData() {
// 1. Validate we have the original barcode
@@ -357,8 +364,12 @@ function saveSmartScanData() {
.then(data => {
if (data.success) {
document.getElementById('fieldsModal').style.display = 'none';
// Optional: Small delay to let the user see it worked
setTimeout(() => location.reload(), 300);
if (data.detail_id && data.data) {
addScanToList(data.detail_id, data.data, data.data.duplicate_status);
}
currentDupKeyValue = '';
smartInput.value = '';
smartInput.focus();
} else if (data.needs_input) {
alert("Error: Please fill in all required fields.");
} else {
@@ -372,6 +383,25 @@ function resetSmartScan() {
document.getElementById('smartScanner').focus();
document.getElementById('routerFeedback').style.display = 'none';
}
// --- ERROR BEEP ---
function playErrorBeep() {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = ctx.createOscillator();
const gainNode = ctx.createGain();
oscillator.connect(gainNode);
gainNode.connect(ctx.destination);
oscillator.type = 'square';
oscillator.frequency.setValueAtTime(520, ctx.currentTime);
gainNode.gain.setValueAtTime(0.3, ctx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.4);
oscillator.start(ctx.currentTime);
oscillator.stop(ctx.currentTime + 0.4);
} catch(e) {
console.warn('Audio not available:', e);
}
}
// Standard variables
let currentDupKeyValue = '';
let currentDuplicateStatus = '';

View File

@@ -1303,9 +1303,25 @@ body {
overflow-y: auto;
}
.scan-header-row {
display: grid;
grid-template-columns: repeat(var(--field-count), 1fr) auto;
gap: var(--space-md);
padding: var(--space-xs) var(--space-md);
max-height: none;
overflow-y: visible;
}
.scan-col-header {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-text-muted);
}
.scan-row {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1.5fr;
grid-template-columns: repeat(var(--field-count), 1fr) 1fr;
gap: var(--space-md);
padding: var(--space-md);
background: var(--color-bg);