Files
TieMeasureFlow/client/static/js/barcode.js
T
Adriano a386986c17 feat: FASE 3 - Flusso MeasurementTec (selezione ricetta, esecuzione misure, riepilogo)
Implementazione completa del flusso operativo per il ruolo MeasurementTec:

Blueprint measure.py:
- select_recipe: selezione ricetta con ricerca e barcode
- task_list: lista task con conteggi subtask e allegati
- task_execute: esecuzione misure con numpad, calibro USB, feedback real-time
- task_complete: riepilogo con statistiche pass/fail e export CSV
- API AJAX: lookup-barcode, save-traceability, save-measurement
- Autorizzazione role_required("MeasurementTec") su tutte le route

Componenti riutilizzabili:
- numpad.html/js/css: tastierino numerico touch-friendly con keyboard support
- caliper_status.html + caliper.js: integrazione calibro USB via Web Serial API
- barcode_scanner.html + barcode.js: scansione barcode con html5-qrcode
- measurement_feedback.html: feedback visivo pass/warning/fail in tempo reale
- next_measurement.html: indicatore prossima misurazione
- annotation-viewer.js: visualizzatore canvas con marker su disegni tecnici
- csv-export.js: export CSV con locale italiano (delimitatore ;, decimale ,)

Sicurezza:
- Decoratore role_required(*roles) per autorizzazione basata su ruoli
- CSRF token su tutti i POST AJAX
- |tojson per prevenire XSS su annotations_json
- Validazione input lato client e server

i18n: 23+ nuove chiavi tradotte IT/EN per tutti i template FASE 3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 08:40:58 +01:00

133 lines
3.2 KiB
JavaScript

/**
* Barcode Scanner Manager
* Integration with html5-qrcode library
* Supports QR codes, barcodes, and camera scanning
*/
function barcodeScanner() {
return {
scanning: false,
result: null,
error: null,
scanner: null,
cameraId: null,
async startScan() {
this.scanning = true;
this.result = null;
this.error = null;
// Check library availability
if (!window.Html5Qrcode) {
this.error = 'Scanner library not loaded';
this.scanning = false;
return;
}
try {
// Get available cameras
const devices = await Html5Qrcode.getCameras();
if (!devices || devices.length === 0) {
this.error = 'Nessuna fotocamera disponibile';
this.scanning = false;
return;
}
// Prefer back camera
const backCamera = devices.find(d => d.label.toLowerCase().includes('back'));
this.cameraId = backCamera ? backCamera.id : devices[0].id;
// Initialize scanner
this.scanner = new Html5Qrcode("barcode-reader");
// Start scanning
await this.scanner.start(
this.cameraId,
{
fps: 10,
qrbox: { width: 250, height: 250 },
aspectRatio: 1.0
},
(decodedText, decodedResult) => {
// Success callback
this.result = decodedText;
this.stopScan();
// Dispatch event for parent components
this.$dispatch('barcode-scanned', {
code: decodedText,
format: decodedResult.result?.format?.formatName || 'unknown',
timestamp: new Date().toISOString()
});
},
(errorMessage) => {
// Error callback - ignore continuous scanning errors
// Only log non-routine errors
if (!errorMessage.includes('NotFoundException')) {
console.debug('Scan frame error:', errorMessage);
}
}
);
} catch (err) {
console.error('Scanner initialization error:', err);
this.error = 'Impossibile accedere alla fotocamera';
this.scanning = false;
}
},
async stopScan() {
if (this.scanner) {
try {
await this.scanner.stop();
await this.scanner.clear();
} catch (err) {
console.error('Stop scan error:', err);
}
this.scanner = null;
}
this.scanning = false;
},
clear() {
this.result = null;
this.error = null;
},
// Cleanup on component destroy
destroy() {
this.stopScan();
}
};
}
/**
* Barcode Input Helper
* For integrating barcode scanning into input fields
*/
function barcodeInput() {
return {
value: '',
showScanner: false,
openScanner() {
this.showScanner = true;
},
handleScan(event) {
this.value = event.detail.code;
this.showScanner = false;
// Dispatch value change event
this.$dispatch('input', { value: this.value });
this.$dispatch('change', { value: this.value });
},
clear() {
this.value = '';
this.$dispatch('input', { value: '' });
}
};
}