2022-06-11 23:44:44 +02:00
|
|
|
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column col-md-12 col-mx-auto col-6">
|
|
|
|
<h1>{{name}}</h1>
|
|
|
|
<h2>Modus</h2>
|
|
|
|
<div class="form-group">
|
|
|
|
<label class="form-radio form-inline">
|
2022-12-15 18:27:42 +01:00
|
|
|
<input type="radio" name="mode" value="false" checked="" onclick="reselect()"><i class="form-icon"></i>
|
|
|
|
Toevoegen
|
2022-06-11 23:44:44 +02:00
|
|
|
</label>
|
|
|
|
<label class="form-radio form-inline">
|
2022-12-15 18:27:42 +01:00
|
|
|
<input type="radio" name="mode" value="true" onclick="reselect()"><i class="form-icon"></i> Controleren
|
2022-06-11 23:44:44 +02:00
|
|
|
</label>
|
|
|
|
</div>
|
2022-06-28 20:09:24 +02:00
|
|
|
<h2>Informatie</h2>
|
2022-06-11 23:44:44 +02:00
|
|
|
<div>
|
2022-12-15 18:27:42 +01:00
|
|
|
<p><select id="videoSource"></select><button class="btn btn-primary" id="start_scan_btn"
|
|
|
|
onclick="reselect()">Start
|
2022-06-11 23:44:44 +02:00
|
|
|
Scannen</button> </p>
|
2022-06-12 01:06:46 +02:00
|
|
|
<div class="columns">
|
2022-12-23 20:22:38 +01:00
|
|
|
<video autoplay playsinline controls="false" class="column col-12" id="webcam_holder" hidden></video>
|
2022-06-12 01:06:46 +02:00
|
|
|
</div>
|
2022-06-28 20:09:24 +02:00
|
|
|
<h4><span id="last-scanned">Nog geen gescand</span></h4>
|
|
|
|
<h3><span id="scan-status">Nog geen gescand</span></h3>
|
|
|
|
<h3>Binnen/Betaald: <span id="stats">Laden...</span></h3>
|
2022-11-12 22:19:27 +01:00
|
|
|
<h3>
|
|
|
|
<label class="form-checkbox form-inline">
|
2022-12-15 18:11:32 +01:00
|
|
|
<input type="checkbox" id="muntjes" onclick="reselect()"><i class="form-icon"></i> Muntjes kopen
|
2022-11-12 22:19:27 +01:00
|
|
|
</label>
|
|
|
|
</h3>
|
2022-06-11 23:44:44 +02:00
|
|
|
</div>
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column col-md-12 col-6">
|
|
|
|
<h2>Handmatig Toevoegen</h2>
|
2022-06-23 17:09:28 +02:00
|
|
|
<form id="student_number_wrapper">
|
|
|
|
<p><label for="student_number">Leerlingnummer:</label></p>
|
|
|
|
<input type="text" id="student_number" name="student_number" placeholder="Leerlingnummer">
|
|
|
|
<button class="btn btn-primary" id="add_student">"Scan"</button>
|
|
|
|
</form>
|
2022-06-11 23:44:44 +02:00
|
|
|
</div>
|
|
|
|
<div class="column col-md-12 col-6">
|
|
|
|
<h2>Extra opties</h2>
|
|
|
|
<p><a href="/party/{{id}}/lijst">Bekijk een tabel in je browser</a></p>
|
|
|
|
<p><a href="/party/{{id}}/export">Exporteer als CSV</a></p>
|
2022-06-23 17:09:28 +02:00
|
|
|
<p><label for="file-selector">Importeer CSV:</label><input type="file" id="file-selector"
|
|
|
|
accept=".csv" /></p>
|
|
|
|
<p><button id="file-selector-button">Importeer</button> <span id="import-result"></span></p>
|
2022-06-11 23:44:44 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<input type="hidden" id="party_id" value="{{id}}">
|
|
|
|
<script src="//unpkg.com/javascript-barcode-reader"></script>
|
|
|
|
<script>
|
|
|
|
function set_colour(state) {
|
|
|
|
if (state == "OK") {
|
|
|
|
document.body.classList.add("bg-success");
|
|
|
|
setTimeout(function () {
|
|
|
|
document.body.classList.remove("bg-success");
|
|
|
|
}, 1000);
|
2022-11-12 22:19:27 +01:00
|
|
|
} else if (state == "COINS") {
|
|
|
|
document.body.classList.add("bg-primary");
|
|
|
|
setTimeout(function () {
|
|
|
|
document.body.classList.remove("bg-primary");
|
|
|
|
}, 1000);
|
2022-06-11 23:44:44 +02:00
|
|
|
} else if (state == "WARN") {
|
|
|
|
document.body.classList.add("bg-warning");
|
|
|
|
setTimeout(function () {
|
|
|
|
document.body.classList.remove("bg-warning");
|
|
|
|
}, 1000);
|
|
|
|
} else if ("ERR") {
|
|
|
|
document.body.classList.add("bg-error");
|
|
|
|
setTimeout(function () {
|
|
|
|
document.body.classList.remove("bg-error");
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-15 18:11:32 +01:00
|
|
|
function reselect() {
|
|
|
|
let elem = document.getElementById("student_number");
|
|
|
|
elem.focus();
|
|
|
|
elem.select();
|
|
|
|
}
|
|
|
|
|
2022-06-11 23:44:44 +02:00
|
|
|
function set_scan_status(status) {
|
|
|
|
document.getElementById("scan-status").innerHTML = status;
|
|
|
|
}
|
|
|
|
|
|
|
|
function set_state(data) {
|
|
|
|
let code = data.code;
|
|
|
|
let state = data.state;
|
|
|
|
let check = document.querySelector('input[name="mode"]:checked').value === "true";
|
|
|
|
|
|
|
|
document.getElementById("last-scanned").innerText = code;
|
|
|
|
if (state === "NotFound") {
|
|
|
|
// leerlingnummer niet gevonden op de gastenlijst
|
|
|
|
// alert("Leerlingnummer niet gevonden");
|
|
|
|
set_scan_status("Heeft (waarschijnlijk) niet betaald");
|
|
|
|
set_colour("ERR");
|
|
|
|
} else if (state === "Found") {
|
|
|
|
// leerlingnummer is gevonden en mag naar binnen
|
|
|
|
// alert("Leerlingnummer gevonden");
|
|
|
|
set_scan_status("Mag naar binnen");
|
|
|
|
set_colour("OK");
|
2022-11-12 22:19:27 +01:00
|
|
|
} else if (state === "Coins") {
|
|
|
|
// leerlingnummer is gevonden en mag naar binnen
|
|
|
|
// alert("Leerlingnummer gevonden");
|
|
|
|
set_scan_status("Mag naar binnen, heeft muntjes gekocht!");
|
|
|
|
set_colour("COINS");
|
2022-06-11 23:44:44 +02:00
|
|
|
} else if (state === "Added") {
|
|
|
|
// leerlingummer is toegevoegd aan de gastenlijst
|
|
|
|
// alert("Leerlingnummer toegevoegd");
|
|
|
|
set_scan_status("Toegevoegd");
|
|
|
|
set_colour("OK");
|
|
|
|
} else if (state === "AlreadyScanned") {
|
|
|
|
// leerlingnummer is al gescand, bij check==false is er al betaald, bij check==true is de leerling al binnen
|
|
|
|
if (check) {
|
|
|
|
// alert("Leerlingnummer is al binnen");
|
|
|
|
set_scan_status("Al binnen");
|
|
|
|
set_colour("ERR");
|
|
|
|
} else {
|
|
|
|
// alert("Leerlingnummer is al betaald");
|
|
|
|
set_scan_status("Al betaald");
|
|
|
|
set_colour("WARN");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
alert("Error");
|
|
|
|
}
|
2022-06-28 20:09:24 +02:00
|
|
|
|
|
|
|
fetch_stats()
|
2022-06-11 23:44:44 +02:00
|
|
|
}
|
2022-06-28 20:09:24 +02:00
|
|
|
|
|
|
|
function fetch_stats() {
|
|
|
|
let party_id = +document.getElementById("party_id").value;
|
|
|
|
fetch(`/api/party/${party_id}/stats`)
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {
|
|
|
|
document.getElementById("stats").innerText = `${data.inside}/${data.total}`;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-11-12 22:36:08 +01:00
|
|
|
function scan_ticket(code, coins) {
|
2022-11-12 22:19:27 +01:00
|
|
|
if (code === "COINS" || code === "000000") {
|
|
|
|
document.getElementById("muntjes").checked = true;
|
|
|
|
return;
|
|
|
|
}
|
2022-06-11 23:44:44 +02:00
|
|
|
let check = document.querySelector('input[name="mode"]:checked').value === "true";
|
|
|
|
let party_id = +document.getElementById("party_id").value;
|
2022-11-12 22:19:27 +01:00
|
|
|
|
2022-06-11 23:44:44 +02:00
|
|
|
fetch("/api/ticket", {
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
party: party_id,
|
|
|
|
code: code,
|
|
|
|
check: check,
|
2022-11-12 22:36:08 +01:00
|
|
|
coins: coins || document.getElementById("muntjes").checked,
|
2022-06-11 23:44:44 +02:00
|
|
|
})
|
|
|
|
}).then(function (response) {
|
|
|
|
return response.json();
|
|
|
|
}).then(function (data) {
|
|
|
|
console.log(data)
|
2022-11-12 22:19:27 +01:00
|
|
|
document.getElementById("muntjes").checked = false;
|
2022-06-11 23:44:44 +02:00
|
|
|
return data;
|
|
|
|
}).then(set_state);
|
|
|
|
}
|
2022-06-28 19:52:11 +02:00
|
|
|
|
2022-06-23 17:09:28 +02:00
|
|
|
function return_handler(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
let code = document.getElementById("student_number").value;
|
|
|
|
console.log(code);
|
|
|
|
scan_ticket(code);
|
2022-06-28 19:32:12 +02:00
|
|
|
document.getElementById("student_number").value = "";
|
2022-06-23 17:09:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
document.getElementById("student_number_wrapper").addEventListener("submit", return_handler);
|
2022-06-11 23:44:44 +02:00
|
|
|
document.getElementById("start_scan_btn").addEventListener('click', start_scanning);
|
|
|
|
|
|
|
|
const videoElement = document.querySelector("video");
|
|
|
|
const videoSelect = document.querySelector("select#videoSource");
|
|
|
|
|
|
|
|
function start_scanning() {
|
2022-12-23 20:22:38 +01:00
|
|
|
document.getElementById("webcam_holder").hidden = false;
|
2022-06-11 23:44:44 +02:00
|
|
|
navigator.mediaDevices
|
|
|
|
.enumerateDevices()
|
|
|
|
.then(gotDevices)
|
|
|
|
.then(getStream)
|
|
|
|
.catch(handleError);
|
|
|
|
}
|
|
|
|
|
|
|
|
videoSelect.onchange = getStream;
|
|
|
|
|
|
|
|
function gotDevices(deviceInfos) {
|
|
|
|
for (let i = 0; i !== deviceInfos.length; ++i) {
|
|
|
|
const deviceInfo = deviceInfos[i];
|
|
|
|
const option = document.createElement("option");
|
|
|
|
option.value = deviceInfo.deviceId;
|
|
|
|
if (deviceInfo.kind === "videoinput") {
|
|
|
|
option.text = deviceInfo.label || "camera " + (videoSelect.length + 1);
|
|
|
|
videoSelect.appendChild(option);
|
|
|
|
} else {
|
|
|
|
console.log("Found another kind of device: ", deviceInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getStream() {
|
|
|
|
if (window.stream) {
|
|
|
|
window.stream.getTracks().forEach(function (track) {
|
|
|
|
track.stop();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const constraints = {
|
|
|
|
video: {
|
|
|
|
deviceId: { exact: videoSelect.value },
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
navigator.mediaDevices
|
|
|
|
.getUserMedia(constraints)
|
|
|
|
.then(gotStream)
|
|
|
|
.catch(handleError);
|
|
|
|
}
|
|
|
|
|
|
|
|
function gotStream(stream) {
|
|
|
|
window.stream = stream; // make stream available to console
|
|
|
|
videoElement.srcObject = stream;
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
console.log("scanning now?")
|
2022-06-12 09:28:46 +02:00
|
|
|
const canvas = document.createElement("canvas");
|
2022-06-11 23:44:44 +02:00
|
|
|
canvas.width = videoElement.videoWidth;
|
|
|
|
canvas.height = videoElement.videoHeight;
|
|
|
|
canvas.getContext("2d").drawImage(videoElement, 0, 0);
|
|
|
|
do_scan(canvas);
|
|
|
|
|
|
|
|
}, 500);
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleError(error) {
|
|
|
|
console.error("Error: ", error);
|
|
|
|
}
|
|
|
|
|
|
|
|
let last_scan = "";
|
|
|
|
|
|
|
|
function do_scan(imageData) {
|
|
|
|
javascriptBarcodeReader({
|
|
|
|
image: imageData,
|
|
|
|
barcode: 'codabar',
|
|
|
|
options: {
|
|
|
|
useAdaptiveThreshold: true,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.then(code => {
|
|
|
|
if (code.length == 6) {
|
|
|
|
if (last_scan === code) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
last_scan = code;
|
|
|
|
scan_ticket(code);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
console.log(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-23 17:09:28 +02:00
|
|
|
function upload_csv() {
|
|
|
|
let file = document.getElementById("file-selector").files[0];
|
|
|
|
if (file) {
|
2022-12-23 20:22:38 +01:00
|
|
|
set_scan_status("CSV uploaden...");
|
|
|
|
// upload file contents to server
|
|
|
|
let reader = new FileReader();
|
|
|
|
reader.onload = function (e) {
|
|
|
|
let contents = e.target.result;
|
|
|
|
let party_id = +document.getElementById("party_id").value;
|
|
|
|
fetch("/api/csv", {
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
party: party_id,
|
|
|
|
csv: contents,
|
|
|
|
})
|
|
|
|
}).then(function (response) {
|
|
|
|
return response.json();
|
|
|
|
}).then(function (data) {
|
|
|
|
console.log(data)
|
|
|
|
set_scan_status(`CSV geupload: ${data.total} regels, waarvan ${data.scanned} leerlingen, ${data.with_coins} met muntjes`);
|
|
|
|
});
|
|
|
|
};
|
2022-06-23 17:09:28 +02:00
|
|
|
reader.readAsText(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
document.getElementById("file-selector-button").addEventListener("click", upload_csv);
|
|
|
|
|
2022-06-28 19:52:11 +02:00
|
|
|
|
2022-06-28 20:09:24 +02:00
|
|
|
fetch_stats();
|
2022-12-15 18:11:32 +01:00
|
|
|
reselect();
|
2022-06-28 19:52:11 +02:00
|
|
|
|
2022-06-11 23:44:44 +02:00
|
|
|
</script>
|
|
|
|
{% endblock %}
|