You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
10 KiB
258 lines
10 KiB
{% extends 'base.html' %} |
|
{% block content %} |
|
{% load static %} |
|
<div class="container-fluid mt-4"> |
|
<div class="row"> |
|
<div class="col-12"> |
|
<div class="card shadow mb-4"> |
|
<div class="card-header py-3"> |
|
<h6 class="m-0 font-weight-bold text-primary">Scanner un coureur</h6> |
|
</div> |
|
<div class="card-body"> |
|
{% if course and course.fin %} |
|
<div class="alert alert-danger text-center mb-0">Cette course est terminée. Le scan n'est plus possible.</div> |
|
{% elif course and not course.debut %} |
|
<div class="alert alert-warning text-center mb-0">Cette course n'a pas encore démarré. Le scan n'est pas possible.</div> |
|
{% else %} |
|
<div id="reader" style="width:100%; max-width:400px; margin:auto;"></div> |
|
<div class="mt-4 position-relative" style="max-width:400px; margin:auto;"> |
|
<label id="manualCoureurLabel">Saisie manuelle :</label> |
|
<div class="input-group align-items-center"> |
|
<input type="text" id="manualCoureurInput" class="form-control" placeholder="Nom ou dossard..." autocomplete="off" aria-label="Saisir un coureur" aria-describedby="manualCoureurLabel"> |
|
<!-- plus de bouton OK, déclenchement direct sur sélection --> |
|
</div> |
|
<div id="manualCoureurSuggestions" class="list-group position-absolute w-100" style="z-index:1000;"></div> |
|
</div> |
|
<div id="scanResult" class="mt-3"></div> |
|
{% if error %} |
|
<div class="alert alert-danger mt-3">{{ error }}</div> |
|
{% endif %} |
|
{% endif %} |
|
</div> |
|
</div> |
|
<div class="card shadow mb-4"> |
|
<div class="card-header py-3"> |
|
<h6 class="m-0 font-weight-bold text-primary">Actions</h6> |
|
</div> |
|
<div class="card-body d-flex justify-content-center gap-3"> |
|
<a href="/" class="btn btn-secondary" title="Accueil"> |
|
<i class="fas fa-home mx-auto"></i> |
|
</a> |
|
<button id="toggleBeep" type="button" class="btn btn-info " title="Bip scan" > |
|
<i id="beepIcon" class="fas fa-volume-up mx-auto"></i> |
|
</button> |
|
<button id="showCameras" type="button" class="btn btn-warning " title="Changer de caméra" data-toggle="modal" data-target="#cameraModal"> |
|
<i class="fas fa-camera mx-auto"></i> |
|
</button> |
|
</div> |
|
<!-- Modal pour la liste des caméras --> |
|
<div class="modal fade" id="cameraModal" tabindex="-1" role="dialog" aria-labelledby="cameraModalLabel" aria-hidden="true"> |
|
<div class="modal-dialog" role="document"> |
|
<div class="modal-content"> |
|
<div class="modal-header"> |
|
<h5 class="modal-title" id="cameraModalLabel">Sélectionner une caméra</h5> |
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> |
|
<span aria-hidden="true">×</span> |
|
</button> |
|
</div> |
|
<div class="modal-body"> |
|
<ul id="cameraList" class="list-group"></ul> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
{% endblock %} |
|
{% block extra_js %} |
|
<script src="{% static 'html5-qrcode/html5-qrcode.min.js' %}"></script> |
|
<script> |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
let selectedCoureurId = null; |
|
const input = document.getElementById('manualCoureurInput'); |
|
const suggestions = document.getElementById('manualCoureurSuggestions'); |
|
if (input) { |
|
input.addEventListener('input', function() { |
|
const val = this.value.trim(); |
|
selectedCoureurId = null; |
|
suggestions.innerHTML = ''; |
|
if (val.length < 2) return; |
|
fetch('/ajax/coureur_autocomplete/?q=' + encodeURIComponent(val)) |
|
.then(r => r.json()) |
|
.then(data => { |
|
suggestions.innerHTML = ''; |
|
data.forEach(c => { |
|
const item = document.createElement('a'); |
|
item.className = 'list-group-item list-group-item-action'; |
|
item.textContent = c.label; |
|
item.style.cursor = 'pointer'; |
|
item.onclick = function() { |
|
input.value = c.label; |
|
selectedCoureurId = c.id; |
|
suggestions.innerHTML = ''; |
|
onScanSuccess(selectedCoureurId, ""); |
|
setTimeout(() => { input.value = ''; }, 100); |
|
}; |
|
suggestions.appendChild(item); |
|
}); |
|
}); |
|
}); |
|
// Cacher suggestions si clic ailleurs |
|
document.addEventListener('click', function(e) { |
|
if (!input.contains(e.target) && !suggestions.contains(e.target)) { |
|
suggestions.innerHTML = ''; |
|
} |
|
}); |
|
} |
|
// plus de bouton OK, plus de gestion de clic |
|
}); |
|
|
|
function playSound(type) { |
|
if (typeof window.beepEnabled !== 'undefined' && !window.beepEnabled) return; |
|
let audio; |
|
if (type === 'ok') { |
|
audio = new Audio("{% static 'mp3/ok.mp3' %}"); |
|
} else { |
|
audio = new Audio("{% static 'mp3/error.mp3' %}"); |
|
} |
|
audio.play(); |
|
} |
|
|
|
|
|
let lastScanned = ''; |
|
let html5Qrcode; |
|
|
|
function getCookie(name) { |
|
var cookieValue = null; |
|
if (document.cookie && document.cookie !== '') { |
|
var cookies = document.cookie.split(';'); |
|
for (var i = 0; i < cookies.length; i++) { |
|
var cookie = jQuery.trim(cookies[i]); |
|
// Does this cookie string begin with the name we want? |
|
if (cookie.substring(0, name.length + 1) === (name + '=')) { |
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); |
|
break; |
|
} |
|
} |
|
} |
|
return cookieValue; |
|
} |
|
|
|
let camerasAvailable = []; |
|
let currentCameraId = null; |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
html5Qrcode = new Html5Qrcode("reader"); |
|
Html5Qrcode.getCameras().then(cameras => { |
|
camerasAvailable = cameras || []; |
|
if (camerasAvailable.length) { |
|
currentCameraId = camerasAvailable[0].id; |
|
html5Qrcode.start( |
|
currentCameraId, |
|
{ fps: 10, qrbox: 250 }, |
|
onScanSuccess |
|
); |
|
} |
|
}); |
|
|
|
// Remplir la liste des caméras à chaque ouverture du modal |
|
$('#cameraModal').on('show.bs.modal', function () { |
|
const cameraList = document.getElementById('cameraList'); |
|
cameraList.innerHTML = ''; |
|
camerasAvailable.forEach((cam, idx) => { |
|
const li = document.createElement('li'); |
|
li.className = 'list-group-item list-group-item-action'; |
|
li.textContent = cam.label || `Caméra ${idx+1}`; |
|
li.style.cursor = 'pointer'; |
|
li.onclick = function() { |
|
if (currentCameraId !== cam.id) { |
|
html5Qrcode.stop().then(() => { |
|
currentCameraId = cam.id; |
|
html5Qrcode.start( |
|
currentCameraId, |
|
{ fps: 10, qrbox: 250 }, |
|
onScanSuccess |
|
); |
|
}); |
|
} |
|
// Fermer le modal |
|
$('#cameraModal').modal('hide'); |
|
}; |
|
cameraList.appendChild(li); |
|
}); |
|
}); |
|
}); |
|
function getCourseIdFromUrl() { |
|
const params = new URLSearchParams(window.location.search); |
|
return params.get('course_id'); |
|
} |
|
function onScanSuccess(decodedText, decodedResult) { |
|
console.log('Scan détecté :', decodedText, 'Course:', getCourseIdFromUrl()); |
|
if (decodedText === lastScanned || window.scanDebounce) return; |
|
window.scanDebounce = true; |
|
lastScanned = decodedText; |
|
const courseId = getCourseIdFromUrl(); |
|
if (!courseId) { |
|
window.scanDebounce = false; |
|
return; |
|
} |
|
fetch("{% url 'scan' %}" + window.location.search, { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/x-www-form-urlencoded', |
|
'X-CSRFToken': getCookie('csrftoken'), |
|
'X-Requested-With': 'XMLHttpRequest' |
|
}, |
|
body: `course_id=${courseId}&qrcode=${encodeURIComponent(decodedText)}` |
|
}) |
|
.then(response => response.text()) |
|
.then(html => { |
|
document.getElementById('scanResult').innerHTML = html; |
|
// Détecte succès ou erreur selon la présence d'une classe d'erreur dans la réponse |
|
if (html.includes('alert-danger')) { |
|
playSound('error'); |
|
} else { |
|
playSound('ok'); |
|
} |
|
setTimeout(function() { |
|
window.scanDebounce = false; |
|
}, 100); // 100ms de délai avant d'autoriser un nouveau scan |
|
}) |
|
.catch(() => { |
|
playSound('error'); |
|
window.scanDebounce = false; |
|
}); |
|
} |
|
document.addEventListener('DOMContentLoaded', function() { |
|
const beepBtn = document.getElementById('toggleBeep'); |
|
const beepIcon = document.getElementById('beepIcon'); |
|
window.beepEnabled = true; |
|
beepBtn.onclick = function() { |
|
window.beepEnabled = !window.beepEnabled; |
|
if (window.beepEnabled) { |
|
beepIcon.classList.remove('fa-volume-mute'); |
|
beepIcon.classList.add('fa-volume-up'); |
|
beepBtn.title = 'Désactiver bip scan'; |
|
} else { |
|
beepIcon.classList.remove('fa-volume-up'); |
|
beepIcon.classList.add('fa-volume-mute'); |
|
beepBtn.title = 'Activer bip scan'; |
|
} |
|
}; |
|
}); |
|
|
|
Html5Qrcode.getCameras().then(cameras => { |
|
if (cameras && cameras.length) { |
|
html5Qrcode.start( |
|
cameras[0].id, |
|
{ fps: 10, qrbox: 250 }, |
|
onScanSuccess |
|
); |
|
} |
|
}); |
|
</script> |
|
{% endblock %}
|
|
|