From d841727439e42c940d56001f08da4ae2399176a7 Mon Sep 17 00:00:00 2001 From: Philipp Dormann Date: Tue, 22 Apr 2025 19:10:57 +0200 Subject: [PATCH] feat: improved mobile card assignment commit 062daa6a0c0b1ce73508c8bca72255ac6759efc3 Author: Philipp Dormann Date: Tue Apr 22 19:10:26 2025 +0200 wip commit cfc3c44f9f9ca57c2478649c47a6b7286249efc8 Author: Philipp Dormann Date: Tue Apr 22 19:09:19 2025 +0200 wip commit d2bc78065636b89ec11e94a3b6099cdc3da9e3a8 Author: Philipp Dormann Date: Tue Apr 22 19:09:02 2025 +0200 wip commit dc5ea5240c71e3ced4cfe48537a3435953de919e Author: Philipp Dormann Date: Tue Apr 22 18:48:25 2025 +0200 wip commit fcadedaa300303b0ef134f55b3464eb7b80ac588 Author: Philipp Dormann Date: Tue Apr 22 18:47:41 2025 +0200 wip commit 2a4fe2b28f6b53eabf62711ad543ea449c850b37 Author: Philipp Dormann Date: Tue Apr 22 18:40:26 2025 +0200 wip --- src/components/dashboard/Dashboard.svelte | 2 +- src/components/general/CardAssignment.svelte | 563 +++++++++++++++---- src/components/general/QrCodeScanner.svelte | 29 +- src/locales/de.json | 12 + src/locales/en.json | 12 + 5 files changed, 511 insertions(+), 107 deletions(-) diff --git a/src/components/dashboard/Dashboard.svelte b/src/components/dashboard/Dashboard.svelte index 58d40840..7ca8092f 100644 --- a/src/components/dashboard/Dashboard.svelte +++ b/src/components/dashboard/Dashboard.svelte @@ -60,7 +60,7 @@ /> - Card Assignment + {$_('card_assignment_menu')} + import { _ } from "svelte-i18n"; import { RunnerCardService, RunnerService } from "@odit/lfk-client-js"; import QrCodeScanner from "./QrCodeScanner.svelte"; let state = "scan_runner"; let runnerinfo = { id: 0, firstname: "", lastname: "" }; let cardCode = ""; - $: scannerActive = state.includes("scan"); + let scannerActive = true; + function resetAll() { + state = "scan_runner"; + runnerinfo = { id: 0, firstname: "", lastname: "" }; + cardCode = ""; + scannerActive = true; + }
-

Card Assignment for Mobile

- - {#if state.includes("scan_")} +

{$_("card_assignment_for_mobile")}

+ {#if state === "done"} +
+ +

{$_("done")}

+

+ {cardCode}
{runnerinfo.firstname} + {runnerinfo.lastname} [#{runnerinfo.id}] +

+ +
+ {:else if state === "assigning"} +

+ {$_("please_wait_a_moment_while_we_assign_the_card")}
{cardCode} +

+ {:else if state === "error_runner"} +
+ +

{$_("runner_not_found")}

+ +
+ {:else if state === "error_card"} +
+ +

{$_("card_not_found")}

+ +
+ {:else} - {#if state === "scan_runner"} -

Scan Runner (Selfservice QR)

+ {#if runnerinfo.id === 0} +

{$_("scan_runner")}

+

+ {$_("selfservice_qr_registration_barcode")} +

+ {:else} +

+ {runnerinfo.firstname} + {runnerinfo.lastname} +

+

+ ID: #{runnerinfo.id}
created_via: {runnerinfo.created_via}
{runnerinfo.group.name} +

{/if} - {#if state === "scan_card"} -

Runner Scanned

-

{runnerinfo.firstname} {runnerinfo.lastname} [#{runnerinfo.id}]

-

Scan Card (Code 128 Barcode)

- {/if} - { - console.log({ type: "DETECT", code: e.detail.decodedText }); - if (state === "scan_runner") { - if ( - e.detail.decodedText.includes( - "https://portal.lauf-fuer-kaya.de/profile/" - ) - ) { - const runnerID = JSON.parse( - atob( - e.detail.decodedText - .replace("https://portal.lauf-fuer-kaya.de/profile/", "") - .split(".")[1] - ) - ).id; - new Audio("/beep.mp3").play(); - RunnerService.runnerControllerGetOne(runnerID).then((runner) => { - console.log(runner); - runnerinfo = runner; - }); - state = "scan_card"; - } - } - if (state === "scan_card") { - if ( - !e.detail.decodedText.includes( - "https://portal.lauf-fuer-kaya.de/profile/" - ) - ) { - cardCode = e.detail.decodedText; - new Audio("/beep.mp3").play(); - state = "assigning"; - RunnerCardService.runnerCardControllerGetAll().then((cards) => { - console.log(cards); - const card = cards.find((c) => c.code === cardCode); - if (card) { - console.log("card found", card); - RunnerCardService.runnerCardControllerPut(card.id, { - enabled: true, - id: card.id, - runner: runnerinfo.id, - }).then(() => { - state = "done"; - }); + + {/if} + {#if state === "scan_card"} +

{$_("scan_card")}

+

{$_("code_128_barcode")}

+ {/if} + {#if state.includes("scan_")} + {#if scannerActive} + { + if (scannerActive) { + scannerActive = false; + console.log({ type: "DETECT", code: e.detail.decodedText }); + if (runnerinfo.id === 0) { + new Audio("/beep.mp3").play(); + if ( + e.detail.decodedText.includes( + "https://portal.lauf-fuer-kaya.de/profile/" + ) + ) { + const runnerID = JSON.parse( + atob( + e.detail.decodedText + .replace("https://portal.lauf-fuer-kaya.de/profile/", "") + .split(".")[1] + ) + ).id; + RunnerService.runnerControllerGetOne(runnerID) + .then((runner) => { + runnerinfo = runner; + }) + .catch((e) => { + console.error(e); + state = "error_runner"; + // resetAll(); + }); } else { - state = "error_card_404"; + const runnerID = parseInt(e.detail.decodedText); + RunnerService.runnerControllerGetOne(runnerID) + .then((runner) => { + runnerinfo = runner; + }) + .catch((e) => { + console.error(e); + state = "error_runner"; + // resetAll(); + }); } - }); + } else { + if (`${e.detail.decodedText}`.length > 10) { + cardCode = e.detail.decodedText; + new Audio("/beep.mp3").play(); + state = "assigning"; + RunnerCardService.runnerCardControllerGetAll() + .then((cards) => { + console.log(cards); + const card = cards.find((c) => c.code === cardCode); + if (card) { + console.log("card found", card); + RunnerCardService.runnerCardControllerPut(card.id, { + enabled: true, + id: card.id, + runner: runnerinfo.id, + }) + .then(() => { + state = "done"; + }) + .catch(() => { + state = "error_card"; + scannerActive = false; + }); + } else { + scannerActive = true; + } + }) + .catch(() => { + scannerActive = true; + }); + } + } } - } - }} - width={320} - height={320} - class="w-full max-w-sm bg-neutral-300 rounded-lg overflow-hidden" - /> - {#if state === "scan_card"} + }} + width={320} + height={320} + class="w-full max-w-sm bg-neutral-300 rounded-lg overflow-hidden" + /> + {/if} + {#if runnerinfo.id !== 0 && state !== "scan_card"} + + {/if} + {#if state === "scan_card" || runnerinfo.id !== 0} - {/if} - - {:else} - - {#if state === "assigning"} -

Assigning Card {cardCode} ⌛

-

Please wait a moment while we assign the card...

- {/if} - {#if state === "done"} -

- Assigned Card {cardCode} to {runnerinfo.firstname} - {runnerinfo.lastname} [#{runnerinfo.id}] ✅ -

- {/if} diff --git a/src/components/general/QrCodeScanner.svelte b/src/components/general/QrCodeScanner.svelte index 0bdc726b..d4671b07 100644 --- a/src/components/general/QrCodeScanner.svelte +++ b/src/components/general/QrCodeScanner.svelte @@ -13,9 +13,25 @@ const dispatch = createEventDispatcher(); + const debounceFunc = (func, delay) => { + let timer; + return function (...args) { + const context = this; + clearTimeout(timer); + timer = setTimeout(() => { + func.apply(context, args); + }, delay); + }; + }; + + const debouncedDispatch = debounceFunc(function (decodedText) { + console.log("dispatchEvent"); + dispatch("detect", { decodedText }); + }, 500); + function onScanSuccess(decodedText, decodedResult) { if (!paused) { - dispatch("detect", { decodedText }); + debouncedDispatch(decodedText); } } @@ -32,6 +48,7 @@ "qr-scanner", { fps: 10, + showTorchButtonIfSupported: true, rememberLastUsedCamera: true, qrbox: { width, height }, aspectRatio: 1, @@ -48,16 +65,6 @@ ); scanner.render(onScanSuccess, onScanFailure); }); - - // pause/resume scanner to avoid unintended scans - $: togglePause(paused); - function togglePause(paused) { - if (paused && scanner?.getState() === Html5QrcodeScannerState.SCANNING) { - scanner?.pause(); - } else if (scanner?.getState() === Html5QrcodeScannerState.PAUSED) { - scanner?.resume(); - } - }
diff --git a/src/locales/de.json b/src/locales/de.json index 89b4efd6..db62c8e6 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -64,6 +64,9 @@ "card-added": "Karte wurde erstellt", "card-deleted": "Karte gelöscht", "card-updated": "Karte aktualisiert", + "card_assignment_for_mobile": "Mobile Kartenzuweisung", + "card_assignment_menu": "Kartenzuweisung", + "card_not_found": "Läuferkarte nicht gefunden...", "cards": "Läuferkarten", "cards-deleted": "Karten gelöscht", "certificates": "Urkunden", @@ -74,6 +77,7 @@ "click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren", "close": "Schließen", "code": "Code", + "code_128_barcode": "Code 128 Barcode", "config-codes": "Konfigurations-Codes", "confirm": "Bestätigen", "confirm-delete": "Löschung Bestätigen", @@ -194,6 +198,7 @@ "donation_added": "Sponsoring hinzugefügt", "donations": "Sponsorings", "donations-are-being-loaded": "Sponsorings werden geladen...", + "done": "✅ Fertig", "donor": "Sponsor", "donor-added": "Sponsor hinzugefügt", "donor-deleted": "Sponsor gelöscht", @@ -296,6 +301,7 @@ "name": "Name", "name-is-required": "Der Gruppenname muss angegeben werden", "new-password": "Neues Passwort", + "next_runner": "Nächster Läufer", "no-address": "Keine Adresse hinterlegt", "no-contact-found": "Keine Kontakte gefunden", "no-contact-selected": "Kein Kontakt ausgewählt", @@ -357,6 +363,7 @@ "please-provide-the-required-information-to-create-a-new-statsclient": "Bitte gebe alle für einen Statsclient notwendigen Informationen an", "please-request-a-new-reset-mail": "Bitte eine neue Passwortreset-Mail anfordern...", "please-wait-a-moment-your-login-is-still-being-processed": "Bitte warte einen Moment, deine Anmeldung wird verarbeitet", + "please_wait_a_moment_while_we_assign_the_card": "Karte wird zugewiesen...", "prefix": "Prefix", "privacy": "Datenschutz", "privacy-loading": "Datenschutzerklärung lädt...", @@ -376,6 +383,7 @@ "runner-import": "Läufer Import", "runner-is-being-added": "Läufer wird hinzugefügt...", "runner-updated": "Läufer aktualisiert!", + "runner_not_found": "Läufer nicht gefunden...", "runner_via_selfservice": "Läufer via Selfservice", "runnercards": "Laeuferkarten", "runnerimport_verify_runners_org": "Bitte die Läufer für den Import in die Organisation \"{org_name}\" bestätigen", @@ -389,6 +397,8 @@ "scan-deleted": "Scan gelöscht", "scan-is-being-updated": "Scan wird aktualisiert", "scan-with-fixed-distance": "Scan mit Festdistanz", + "scan_card": "Läuferkarte scannen", + "scan_runner": "Läufer scannen", "scans": "Scans", "scans-are-being-loaded": "Scans werden geladen", "scanstation": "Scanner Station", @@ -404,6 +414,7 @@ "select-all": "Alle auswählen", "select-language": "Sprache auswählen", "selfservice-registration": "Selfservice Registrierung", + "selfservice_qr_registration_barcode": "Kiosk QR/ Selfservice Barcode", "send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services", "set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen", "settings": "Einstellungen", @@ -472,6 +483,7 @@ "track-updated": "Laufstrecke aktualisiert", "track-was-updated": "Laufstrecke wurde aktualisiert", "tracks": "Laufstrecken", + "try_again": "erneut versuchen", "unpaid": "Offen", "update-card": "Karte aktualisieren", "update-password": "Passwort ändern", diff --git a/src/locales/en.json b/src/locales/en.json index fb1ffee3..b94bd1c4 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -64,6 +64,9 @@ "card-added": "Card added", "card-deleted": "Card deleted", "card-updated": "Card updated", + "card_assignment_for_mobile": "Card Assignment for Mobile", + "card_assignment_menu": "Card Assignment", + "card_not_found": "Card not found...", "cards": "Cards", "cards-deleted": "Cards deleted", "certificates": "Certificates", @@ -74,6 +77,7 @@ "click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard", "close": "Close", "code": "Code", + "code_128_barcode": "Code 128 Barcode", "config-codes": "Config codes", "confirm": "Confirm", "confirm-delete": "Confirm Delete", @@ -194,6 +198,7 @@ "donation_added": "Donation_added", "donations": "Donations", "donations-are-being-loaded": "donations are being loaded", + "done": "✅ Done", "donor": "Donor", "donor-added": "Donor added", "donor-deleted": "donor deleted", @@ -296,6 +301,7 @@ "name": "Name", "name-is-required": "Name is required", "new-password": "New password", + "next_runner": "Next Runner", "no-address": "no address", "no-contact-found": "No contacts found", "no-contact-selected": "No contact selected", @@ -357,6 +363,7 @@ "please-provide-the-required-information-to-create-a-new-statsclient": "Please provide the required information to create a new statsclient", "please-request-a-new-reset-mail": "Please request a new reset mail...", "please-wait-a-moment-your-login-is-still-being-processed": "Please wait a moment, your login is still being processed", + "please_wait_a_moment_while_we_assign_the_card": "Please wait a moment while we assign the card...", "prefix": "Prefix", "privacy": "Privacy", "privacy-loading": "Privacy loading...", @@ -376,6 +383,7 @@ "runner-import": "Runner Import", "runner-is-being-added": "Runner is being added...", "runner-updated": "Runner updated!", + "runner_not_found": "Runner not found...", "runner_via_selfservice": "Runner via Selfservice", "runnercards": "Runnercards", "runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"", @@ -389,6 +397,8 @@ "scan-deleted": "scan deleted", "scan-is-being-updated": "Scan is being updated", "scan-with-fixed-distance": "Scan with fixed distance", + "scan_card": "Scan Card", + "scan_runner": "Scan Runner", "scans": "Scans", "scans-are-being-loaded": "Scans are being loaded", "scanstation": "Scanstation", @@ -404,6 +414,7 @@ "select-all": "select all", "select-language": "Select language", "selfservice-registration": "Selfservice registration", + "selfservice_qr_registration_barcode": "Kiosk QR/ Selfservice Barcode", "send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services", "set-the-user-active-inactive": "set the user active/ inactive", "settings": "Settings", @@ -472,6 +483,7 @@ "track-updated": "Track deleted", "track-was-updated": "Track was updated!", "tracks": "Tracks", + "try_again": "Try Again", "unpaid": "Unpaid", "update-card": "Update Card", "update-password": "Update password",