feat(tools): Basic mobile scanner
This commit is contained in:
parent
51d9b35dc4
commit
500886e410
BIN
public/error.mp3
Normal file
BIN
public/error.mp3
Normal file
Binary file not shown.
@ -71,6 +71,7 @@
|
||||
import StatsClients from "./components/statsclients/StatsClients.svelte";
|
||||
import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte";
|
||||
import CardReplacement from "./components/tools/CardReplacement.svelte";
|
||||
import ScanClient from "./components/tools/ScanClient.svelte";
|
||||
store.init();
|
||||
</script>
|
||||
|
||||
@ -140,6 +141,9 @@
|
||||
<Route path="/cardreplacement/">
|
||||
<CardReplacement />
|
||||
</Route>
|
||||
<Route path="/scanclient/">
|
||||
<ScanClient />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="/teams/*">
|
||||
<Route path="/">
|
||||
|
244
src/components/tools/ScanClient.svelte
Normal file
244
src/components/tools/ScanClient.svelte
Normal file
@ -0,0 +1,244 @@
|
||||
<script>
|
||||
import { _, time } from "svelte-i18n";
|
||||
import {
|
||||
RunnerCardService,
|
||||
RunnerService,
|
||||
ScanService,
|
||||
ScanStationService,
|
||||
TrackService,
|
||||
} from "@odit/lfk-client-js";
|
||||
import QrCodeScanner from "./QrCodeScanner.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import Select from "svelte-select";
|
||||
let state = "scan_card";
|
||||
let scaninfo = {
|
||||
lapTime: 0,
|
||||
track: "",
|
||||
distance: null,
|
||||
valid: false,
|
||||
id: 0,
|
||||
runner: {
|
||||
id: 0,
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
distance: 0,
|
||||
},
|
||||
};
|
||||
let cardCode = "";
|
||||
let scannerActive = false;
|
||||
let barcodeInput;
|
||||
let stations = [];
|
||||
let selectedStation = null;
|
||||
|
||||
function resetAll() {
|
||||
state = "scan_card";
|
||||
scaninfo = {
|
||||
lapTime: 0,
|
||||
track: "",
|
||||
distance: null,
|
||||
valid: false,
|
||||
id: 0,
|
||||
runner: {
|
||||
id: 0,
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
distance: 0,
|
||||
},
|
||||
};
|
||||
cardCode = "";
|
||||
scannerActive = true;
|
||||
setTimeout(() => {
|
||||
barcodeInput && barcodeInput.focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (barcodeInput) {
|
||||
barcodeInput.focus();
|
||||
}
|
||||
ScanStationService.scanStationControllerGetAll()
|
||||
.then((data) => {
|
||||
stations = data.map((val) => {
|
||||
return {
|
||||
label: val.description,
|
||||
value: val,
|
||||
};
|
||||
});
|
||||
scannerActive = true;
|
||||
})
|
||||
.catch(() => {
|
||||
stations = [];
|
||||
});
|
||||
});
|
||||
|
||||
function handleInput(input) {
|
||||
if (`${input}`.length > 10) {
|
||||
cardCode = input;
|
||||
|
||||
ScanService.scanControllerPostTrackScans({
|
||||
card: parseInt(cardCode),
|
||||
station: selectedStation,
|
||||
})
|
||||
.then((data) => {
|
||||
scaninfo = data;
|
||||
if (scaninfo.valid) {
|
||||
new Audio("/beep.mp3").play();
|
||||
state = "scan_success";
|
||||
} else {
|
||||
state = "error_invalid";
|
||||
new Audio("/error.mp3").play();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
state = "error_card";
|
||||
new Audio("/error.mp3").play();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="p-4">
|
||||
<h3 class="text-3xl font-bold">{$_("mobile-scanclient")}</h3>
|
||||
<Select
|
||||
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
|
||||
items={stations}
|
||||
showChevron={true}
|
||||
placeholder={$_("search-for-track")}
|
||||
noOptionsMessage={$_("no-tracks-found")}
|
||||
on:select={(selectedValue) => {
|
||||
selectedStation = selectedValue.detail.value.id;
|
||||
setTimeout(() => {
|
||||
barcodeInput && barcodeInput.focus();
|
||||
}, 100);
|
||||
}}
|
||||
on:clear={() => (selectedStation = null)}
|
||||
/>
|
||||
{#if state === "error_card"}
|
||||
<div class="text-center mx-auto">
|
||||
<svg
|
||||
class="h-64 mx-auto"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 500 500"
|
||||
><path
|
||||
d="M298.37 335.5C382 299.85 469.46 233.1 432.31 135 398.6 46 284.74 25.75 219.62 102.47c-28.09 33.09-23.18 77.05-57.16 106.51s-90.4 45.83-75.13 104c23.67 89.93 156 46.02 211.04 22.52Z"
|
||||
style="fill:#407bff"
|
||||
/><path
|
||||
d="M298.37 335.5C382 299.85 469.46 233.1 432.31 135 398.6 46 284.74 25.75 219.62 102.47c-28.09 33.09-23.18 77.05-57.16 106.51s-90.4 45.83-75.13 104c23.67 89.93 156 46.02 211.04 22.52Z"
|
||||
style="fill:#fff;opacity:.9"
|
||||
/><path
|
||||
d="M360.6 263.05h-.36c-26.64-2.18-45-25-45.74-25.92a4.47 4.47 0 0 1 7-5.55c.21.27 15.9 19.61 37.63 22.37 7-7 13-25.48 12.33-31.07v-.16c-.14-1.8-.48-8 1.29-11.65a4.47 4.47 0 0 1 8 3.88c-.44.92-.65 4.23-.44 7 1 9.2-7 32.42-17 40.19a4.47 4.47 0 0 1-2.71.91ZM148.82 238.82a65.8 65.8 0 0 1-48.56-22.28 4.46 4.46 0 0 1-.26-5.64c7.22-9.71 20-32.64 22-40.11a10.91 10.91 0 0 0-4.14-4.33 4.45 4.45 0 0 1-2.55-3.61l-.72-7.32a4.47 4.47 0 0 1 8.89-.88l.5 5.09a22.34 22.34 0 0 1 6.81 8.65 4.48 4.48 0 0 1 .32 2.26c-.92 7.93-13.79 30.9-21.71 42.51 18.49 18.43 40.59 16.75 41.56 16.66a4.47 4.47 0 0 1 .82 8.9c-.26.02-1.29.1-2.96.1ZM292.87 416.09h-12a4.47 4.47 0 0 1-4.31-5.66c3.13-11.24 4.67-20.39 5.82-34.71-4.24-20-8.23-38.21-8.27-38.39a4.47 4.47 0 0 1 8.73-1.91c0 .18 4.12 18.86 8.41 39.08a4.23 4.23 0 0 1 .08 1.28c-1 12.86-2.31 21.75-4.67 31.38h6.18a4.47 4.47 0 0 1 0 8.93ZM200.32 416.09h-6.76a4.45 4.45 0 0 1-4.42-5.08c1.15-8.2 7-23.13 13.3-38.14 2.23-19.8 4.05-36.8 4.07-37a4.47 4.47 0 1 1 8.88 1c0 .17-1.88 17.56-4.15 37.65a4.31 4.31 0 0 1-.32 1.22c-4.43 10.63-9.49 23.15-11.8 31.44h1.2a4.47 4.47 0 1 1 0 8.93Z"
|
||||
style="fill:#263238"
|
||||
/><path
|
||||
d="m204.21 111-52.06 52.07c-2.62 57.71-2.41 118.33 0 181.18h172.16c-3.41-81.1-3.73-159.17 0-233.25Z"
|
||||
style="fill:#fff"
|
||||
/><path
|
||||
d="M324.31 345.13H152.15a.9.9 0 0 1-.9-.86c-2.49-65.27-2.49-126.27 0-181.27a.9.9 0 0 1 .27-.59l52.06-52.07a.89.89 0 0 1 .63-.26h120.1a.9.9 0 0 1 .65.28.87.87 0 0 1 .24.66c-3.59 71.34-3.59 147.61 0 233.17a.89.89 0 0 1-.25.65.86.86 0 0 1-.64.29ZM153 343.34h170.38c-3.54-84.86-3.55-160.59 0-231.47h-118.8L153 163.43c-2.45 54.64-2.45 115.16 0 179.91Z"
|
||||
style="fill:#263238"
|
||||
/><path
|
||||
d="M214.28 219.19c-.2-4.36-2.67-7.8-5.53-7.7s-5 3.71-4.82 8.07 2.67 7.8 5.53 7.69 5.02-3.71 4.82-8.06ZM274.65 217.82c-.2-4.35-2.67-7.79-5.53-7.69s-5 3.71-4.82 8.07 2.68 7.8 5.53 7.69 5.02-3.71 4.82-8.07ZM229.35 237a36.55 36.55 0 0 1 28.63 1.3 1.27 1.27 0 0 1 .49 1.74 1.3 1.3 0 0 1-1.75.49c-.15-.08-14.4-7.76-31.41 1a1.31 1.31 0 0 1-1.74-.54 1.27 1.27 0 0 1 .55-1.72 41.73 41.73 0 0 1 5.23-2.27ZM205.64 178.34a2.64 2.64 0 0 1 1.26.36 2.58 2.58 0 0 1 .92 3.51A25.29 25.29 0 0 1 188.27 195a2.59 2.59 0 0 1-2.69-2.45 2.55 2.55 0 0 1 2.44-2.66c.39 0 9.62-.58 15.36-10.27a2.52 2.52 0 0 1 2.26-1.28ZM266.05 176.87a2.57 2.57 0 0 1 2.33.72c8 8 17.14 6.39 17.52 6.32a2.6 2.6 0 0 1 3 2 2.54 2.54 0 0 1-2 3c-.5.09-12.14 2.31-22.21-7.75a2.54 2.54 0 0 1 1.31-4.3Z"
|
||||
style="fill:#407bff"
|
||||
/><path
|
||||
d="m321.72 204.86-7.31.68a5.22 5.22 0 0 1-5.58-4.06L298.7 156.1a5.22 5.22 0 0 1 3.77-6.18l19.59-5.14ZM209 167.69c-5.09-13.89-10.18-36.12-4.81-56.71l-52.06 52.07c14.73 4.95 38.19 7.06 56.87 4.64Z"
|
||||
style="opacity:.2"
|
||||
/><path
|
||||
d="M204.21 163.05c-5.71-16.86-3.38-39.78 0-52.07l-52.06 52.07c15.76 2.87 33.37 2.41 52.06 0Z"
|
||||
style="fill:#fff"
|
||||
/><path
|
||||
d="M176 165.92a133.14 133.14 0 0 1-24-2 .88.88 0 0 1-.47-1.5l52.06-52.07a.89.89 0 0 1 1.49.87c-3.14 11.44-5.75 34.6 0 51.54a.93.93 0 0 1-.09.76.87.87 0 0 1-.64.41 221.85 221.85 0 0 1-28.35 1.99Zm-22-3.46c13.84 2.29 29.91 2.24 49-.16-4.71-14.94-3.64-34.71-.48-48.4Z"
|
||||
style="fill:#263238"
|
||||
/></svg
|
||||
>
|
||||
<p class="text-lg font-semibold">{$_("card_not_found")}</p>
|
||||
<button
|
||||
on:click={() => {
|
||||
resetAll();
|
||||
}}
|
||||
type="button"
|
||||
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-100 text-blue-800 hover:bg-blue-200 focus:outline-hidden focus:bg-blue-200 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-500 dark:bg-blue-800/30 dark:hover:bg-blue-800/20 dark:focus:bg-blue-800/20 mt-2"
|
||||
>
|
||||
{$_("try_again")}
|
||||
</button>
|
||||
</div>
|
||||
{:else if state === "error_invalid"}
|
||||
<div class="text-center mx-auto">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-64 h-64 text-center mx-auto text-red-600 mt-2"
|
||||
viewBox="5.25 5.25 13.5 13.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-lg font-semibold">{$_("invalid-scan")}</p>
|
||||
<button
|
||||
on:click={() => {
|
||||
resetAll();
|
||||
}}
|
||||
type="button"
|
||||
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-100 text-blue-800 hover:bg-blue-200 focus:outline-hidden focus:bg-blue-200 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-500 dark:bg-blue-800/30 dark:hover:bg-blue-800/20 dark:focus:bg-blue-800/20 mt-2"
|
||||
>
|
||||
{$_("try_again")}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<p>
|
||||
<b>{$_("runner")}:</b>
|
||||
{scaninfo.runner?.firstname}
|
||||
{scaninfo.runner?.lastname}
|
||||
</p>
|
||||
<p>
|
||||
<b>{$_("laptime")}:</b>
|
||||
{Math.floor(scaninfo.lapTime / 60) +
|
||||
"min " +
|
||||
(Math.floor(scaninfo.lapTime % 60) + "").padStart(2, "0") +
|
||||
"s"}
|
||||
</p>
|
||||
<!-- -->
|
||||
{/if}
|
||||
{#if state.includes("scan_")}
|
||||
{#if scannerActive}
|
||||
<QrCodeScanner
|
||||
:paused={!scannerActive}
|
||||
on:detect={(e) => {
|
||||
if (scannerActive) {
|
||||
if (`${e.detail.decodedText}`.length === 13) {
|
||||
e.detail.decodedText = e.detail.decodedText.substring(
|
||||
0,
|
||||
e.detail.decodedText.length - 1
|
||||
);
|
||||
}
|
||||
scannerActive = false;
|
||||
console.log({ type: "DETECT", code: e.detail.decodedText });
|
||||
handleInput(e.detail.decodedText);
|
||||
}
|
||||
}}
|
||||
width={320}
|
||||
height={320}
|
||||
class="w-full max-w-sm bg-neutral-300 rounded-lg overflow-hidden"
|
||||
/>
|
||||
<form
|
||||
on:submit={(e) => {
|
||||
handleInput(barcodeInput.value);
|
||||
barcodeInput.value = "";
|
||||
e.preventDefault();
|
||||
}}
|
||||
class="mt-2"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={$_("barcode_scanner")}
|
||||
class="w-full max-w-sm bg-neutral-300 rounded-lg overflow-hidden mt-2"
|
||||
bind:this={barcodeInput}
|
||||
/>
|
||||
</form>
|
||||
{/if}
|
||||
<!-- -->
|
||||
{/if}
|
||||
</div>
|
@ -296,7 +296,6 @@
|
||||
"logout": "Abmelden",
|
||||
"mail-validation-in-progress": "E-Mail Verifizierung läuft... ",
|
||||
"manage-admin-users": "Nutzer verwalten",
|
||||
"management": "Verwaltung",
|
||||
"middle-name": "Mittelname",
|
||||
"minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)",
|
||||
"minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein",
|
||||
@ -376,7 +375,6 @@
|
||||
"profile-deleted": "Profil gelöscht!",
|
||||
"profile-picture": "Profilbild",
|
||||
"profile-updated": "Profil wurde aktualisiert!",
|
||||
"quick-tools": "Werkzeuge",
|
||||
"read-license": "Lizenz-Text lesen",
|
||||
"receipt-needed": "Spendenquittung benötigt",
|
||||
"repo_link": "Link",
|
||||
@ -441,7 +439,6 @@
|
||||
"status": "Status",
|
||||
"stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können",
|
||||
"successful-password-reset": "Passwort erfolgreich zurückgesetzt!",
|
||||
"system": "System",
|
||||
"team": "Team",
|
||||
"team-added": "Team wurde erstellt",
|
||||
"team-deleted": "Team gelöscht",
|
||||
@ -533,5 +530,9 @@
|
||||
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben",
|
||||
"you-have-to-save-your-changes-to-generate-a-link": "Du musst deine Änderungen speichern, um einen Link zu generieren.",
|
||||
"you-must-create-at-least-one-card-or-cancel": "Du musst mindestens eine Blankokarte erstellen.",
|
||||
"zip-postal-code": "Postleitzahl"
|
||||
"zip-postal-code": "Postleitzahl",
|
||||
"quick-tools": "Werkzeuge",
|
||||
"management": "Verwaltung",
|
||||
"system": "System",
|
||||
"mobile-scanclient": "Mobiler Scanclient"
|
||||
}
|
@ -375,7 +375,6 @@
|
||||
"profile-deleted": "Profile deleted!",
|
||||
"profile-picture": "Profile Picture",
|
||||
"profile-updated": "Profile updated!",
|
||||
"quick-tools": "Tools",
|
||||
"read-license": "Read License",
|
||||
"receipt-needed": "Receipt needed",
|
||||
"repo_link": "Link",
|
||||
@ -440,7 +439,6 @@
|
||||
"status": "Status",
|
||||
"stuff-that-could-harm-your-profile": "Stuff that could harm your profile",
|
||||
"successful-password-reset": "Successful password reset!",
|
||||
"system": "System",
|
||||
"team": "Team",
|
||||
"team-added": "Team added",
|
||||
"team-deleted": "Team deleted",
|
||||
@ -532,5 +530,8 @@
|
||||
"you-have-to-provide-an-organization": "You have to provide an organization",
|
||||
"you-have-to-save-your-changes-to-generate-a-link": "You have to save your changes to generate a link.",
|
||||
"you-must-create-at-least-one-card-or-cancel": "You must create at least one card.",
|
||||
"zip-postal-code": "ZIP/ postal code"
|
||||
"zip-postal-code": "ZIP/ postal code",
|
||||
"quick-tools": "Tools",
|
||||
"system": "System",
|
||||
"mobile-scanclient": "Mobile scanclient"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user