Initial commit
This commit is contained in:
273
src-backup/assets/js/scanner.js
Normal file
273
src-backup/assets/js/scanner.js
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* QR Code Scanner Module
|
||||
*/
|
||||
class QRScanner {
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
scannerId: 'qr-reader',
|
||||
resultId: 'scan-result',
|
||||
facingMode: 'environment',
|
||||
fps: 10,
|
||||
qrbox: { width: 250, height: 250 },
|
||||
...options
|
||||
};
|
||||
|
||||
this.scanner = null;
|
||||
this.isScanning = false;
|
||||
this.lastScanTime = 0;
|
||||
this.scanCooldown = 2000; // 2 seconds cooldown
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.scanner = new Html5Qrcode(this.options.scannerId);
|
||||
|
||||
// Create result container if not exists
|
||||
if (!document.getElementById(this.options.resultId)) {
|
||||
const resultDiv = document.createElement('div');
|
||||
resultDiv.id = this.options.resultId;
|
||||
document.querySelector(`#${this.options.scannerId}`).parentNode.appendChild(resultDiv);
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.isScanning) return;
|
||||
|
||||
try {
|
||||
await this.scanner.start(
|
||||
{ facingMode: this.options.facingMode },
|
||||
this.options,
|
||||
this.onScanSuccess.bind(this),
|
||||
this.onScanError.bind(this)
|
||||
);
|
||||
|
||||
this.isScanning = true;
|
||||
this.updateUI('started');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to start scanner:', error);
|
||||
this.showError('Failed to start camera. Please check permissions.');
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.isScanning) return;
|
||||
|
||||
this.scanner.stop().then(() => {
|
||||
this.isScanning = false;
|
||||
this.updateUI('stopped');
|
||||
}).catch(error => {
|
||||
console.error('Failed to stop scanner:', error);
|
||||
});
|
||||
}
|
||||
|
||||
onScanSuccess(decodedText, decodedResult) {
|
||||
const currentTime = Date.now();
|
||||
|
||||
// Prevent multiple scans in short time
|
||||
if (currentTime - this.lastScanTime < this.scanCooldown) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastScanTime = currentTime;
|
||||
|
||||
// Show scanning indicator
|
||||
this.showResult('scanning', 'Scanning QR code...');
|
||||
|
||||
// Process the scan
|
||||
this.processQRCode(decodedText);
|
||||
}
|
||||
|
||||
onScanError(error) {
|
||||
console.warn('QR Scan Error:', error);
|
||||
// Don't show errors to user unless critical
|
||||
}
|
||||
|
||||
async processQRCode(qrData) {
|
||||
try {
|
||||
const response = await fetch('../api/scan_qr.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ qr_code: qrData })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showResult('success', data);
|
||||
this.playSound('success');
|
||||
|
||||
// Auto-stop after successful scan (optional)
|
||||
if (this.options.autoStop) {
|
||||
setTimeout(() => this.stop(), 3000);
|
||||
}
|
||||
|
||||
} else {
|
||||
this.showResult('error', data.message);
|
||||
this.playSound('error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Processing error:', error);
|
||||
this.showResult('error', 'Network error. Please try again.');
|
||||
this.playSound('error');
|
||||
}
|
||||
}
|
||||
|
||||
showResult(type, data) {
|
||||
const resultDiv = document.getElementById(this.options.resultId);
|
||||
|
||||
let html = '';
|
||||
|
||||
switch(type) {
|
||||
case 'scanning':
|
||||
html = `
|
||||
<div class="alert alert-info">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
${data}
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
|
||||
case 'success':
|
||||
html = `
|
||||
<div class="alert alert-success attendance-success">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-check-circle-fill me-3" style="font-size: 2rem;"></i>
|
||||
<div>
|
||||
<h5 class="mb-1">Attendance Recorded!</h5>
|
||||
<p class="mb-1"><strong>Student:</strong> ${data.data.student_name}</p>
|
||||
<p class="mb-1"><strong>Activity:</strong> ${data.data.activity_name}</p>
|
||||
<p class="mb-0"><strong>Time:</strong> ${data.data.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
html = `
|
||||
<div class="alert alert-danger">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
${data}
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = html;
|
||||
|
||||
// Auto-clear result after some time
|
||||
if (type !== 'scanning') {
|
||||
setTimeout(() => {
|
||||
if (resultDiv.innerHTML.includes(html)) {
|
||||
resultDiv.innerHTML = '';
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
const resultDiv = document.getElementById(this.options.resultId);
|
||||
resultDiv.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
<i class="bi bi-camera-video-off me-2"></i>
|
||||
${message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
updateUI(state) {
|
||||
const scannerDiv = document.getElementById(this.options.scannerId);
|
||||
|
||||
if (state === 'started') {
|
||||
scannerDiv.classList.add('scanning');
|
||||
} else {
|
||||
scannerDiv.classList.remove('scanning');
|
||||
}
|
||||
}
|
||||
|
||||
playSound(type) {
|
||||
// Create audio context for sounds
|
||||
try {
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
if (type === 'success') {
|
||||
oscillator.frequency.setValueAtTime(523.25, audioContext.currentTime); // C5
|
||||
oscillator.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1); // E5
|
||||
oscillator.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2); // G5
|
||||
} else {
|
||||
oscillator.frequency.setValueAtTime(200, audioContext.currentTime); // Low tone
|
||||
}
|
||||
|
||||
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
|
||||
|
||||
oscillator.start();
|
||||
oscillator.stop(audioContext.currentTime + 0.3);
|
||||
|
||||
} catch (error) {
|
||||
console.log('Audio not supported');
|
||||
}
|
||||
}
|
||||
|
||||
toggleCamera() {
|
||||
if (this.options.facingMode === 'environment') {
|
||||
this.options.facingMode = 'user';
|
||||
} else {
|
||||
this.options.facingMode = 'environment';
|
||||
}
|
||||
|
||||
this.stop();
|
||||
setTimeout(() => this.start(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize scanner when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Check for scanner elements on page
|
||||
const scannerElements = document.querySelectorAll('[data-scanner]');
|
||||
|
||||
scannerElements.forEach(element => {
|
||||
const scannerId = element.getAttribute('data-scanner') || 'qr-reader';
|
||||
const scanner = new QRScanner({
|
||||
scannerId: scannerId,
|
||||
autoStop: element.getAttribute('data-auto-stop') === 'true'
|
||||
});
|
||||
|
||||
// Add start/stop buttons if not auto-start
|
||||
if (element.getAttribute('data-auto-start') !== 'true') {
|
||||
const controls = document.createElement('div');
|
||||
controls.className = 'scanner-controls mt-3';
|
||||
controls.innerHTML = `
|
||||
<button class="btn btn-success me-2 start-scan">
|
||||
<i class="bi bi-play-circle"></i> Start Scanner
|
||||
</button>
|
||||
<button class="btn btn-danger me-2 stop-scan">
|
||||
<i class="bi bi-stop-circle"></i> Stop Scanner
|
||||
</button>
|
||||
<button class="btn btn-secondary toggle-camera">
|
||||
<i class="bi bi-camera-video"></i> Switch Camera
|
||||
</button>
|
||||
`;
|
||||
|
||||
element.parentNode.appendChild(controls);
|
||||
|
||||
// Add event listeners
|
||||
controls.querySelector('.start-scan').addEventListener('click', () => scanner.start());
|
||||
controls.querySelector('.stop-scan').addEventListener('click', () => scanner.stop());
|
||||
controls.querySelector('.toggle-camera').addEventListener('click', () => scanner.toggleCamera());
|
||||
} else {
|
||||
// Auto-start scanner
|
||||
setTimeout(() => scanner.start(), 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user