Compare commits

..

4 Commits

Author SHA1 Message Date
fcadedaa30 wip 2025-04-22 18:47:41 +02:00
2a4fe2b28f wip 2025-04-22 18:40:26 +02:00
87df34bb56 chore(release): 1.10.5
All checks were successful
Build release images / build-container (push) Successful in 1m0s
2025-04-22 17:47:22 +02:00
9c3b742a98 feat(runners): add distance sorting 2025-04-22 17:47:04 +02:00
6 changed files with 200 additions and 114 deletions

View File

@@ -2,8 +2,15 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.10.5](https://git.odit.services/lfk/frontend/compare/1.10.4...1.10.5)
- feat(runners): add distance sorting [`9c3b742`](https://git.odit.services/lfk/frontend/commit/9c3b742a98e17837fbcabe308fb2774dc13b03b8)
#### [1.10.4](https://git.odit.services/lfk/frontend/compare/1.10.3...1.10.4) #### [1.10.4](https://git.odit.services/lfk/frontend/compare/1.10.3...1.10.4)
> 22 April 2025
- chore(release): 1.10.4 [`68bf371`](https://git.odit.services/lfk/frontend/commit/68bf3717f9cf15a547c51e245452d4e013f7acad)
- fix(runners): Fix runner overview sort [`f88c6e0`](https://git.odit.services/lfk/frontend/commit/f88c6e0dbab620887e180de0d3eab03b8215a477) - fix(runners): Fix runner overview sort [`f88c6e0`](https://git.odit.services/lfk/frontend/commit/f88c6e0dbab620887e180de0d3eab03b8215a477)
#### [1.10.3](https://git.odit.services/lfk/frontend/compare/1.10.2...1.10.3) #### [1.10.3](https://git.odit.services/lfk/frontend/compare/1.10.2...1.10.3)

View File

@@ -13,7 +13,7 @@
<body> <body>
<span style="display: none; visibility: hidden" id="buildinfo" <span style="display: none; visibility: hidden" id="buildinfo"
>RELEASE_INFO-1.10.4-RELEASE_INFO</span >RELEASE_INFO-1.10.5-RELEASE_INFO</span
> >
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script> <script src="/env.js"></script>

View File

@@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "1.10.4", "version": "1.10.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",

View File

@@ -2,88 +2,184 @@
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js"; import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import QrCodeScanner from "./QrCodeScanner.svelte"; import QrCodeScanner from "./QrCodeScanner.svelte";
let state = "scan_runner"; let state = "scan_runner";
let state_tmp = "";
let runnerinfo = { id: 0, firstname: "", lastname: "" }; let runnerinfo = { id: 0, firstname: "", lastname: "" };
let cardCode = ""; let cardCode = "";
$: scannerActive = state.includes("scan"); let scannerActive = true;
function resetAll() {
state = "scan_runner";
runnerinfo = { id: 0, firstname: "", lastname: "" };
cardCode = "";
scannerActive = true;
}
</script> </script>
<div class="p-4"> <div class="p-4">
<h3 class="text-3xl font-bold">Card Assignment for Mobile</h3> <h3 class="text-3xl font-bold">Card Assignment for Mobile</h3>
<!-- <p>state</p> <!-- <p class="p-4 bg-sky-200 rounded-lg mt-2 mb-2">state: {state}</p> -->
<p>{state}</p> {#if state === "done"}
<p>scannerActive</p> <h3 class="text-2xl font-bold">✅ Done</h3>
<p>{scannerActive}</p> --> <h4 class="text-xl font-semibold">
{#if state.includes("scan_")} Assigned Card {cardCode} to {runnerinfo.firstname}
<!-- --> {runnerinfo.lastname} [#{runnerinfo.id}]
{#if state === "scan_runner"} </h4>
<h3 class="text-xl font-bold">Scan Runner (Selfservice QR)</h3> <button
{/if} on:click={() => {
{#if state === "scan_card"} resetAll();
<h3 class="text-xl font-bold">Runner Scanned</h3>
<p>{runnerinfo.firstname} {runnerinfo.lastname} [#{runnerinfo.id}]</p>
<h3 class="text-xl font-bold">Scan Card (Code 128 Barcode)</h3>
{/if}
<QrCodeScanner
paused={!scannerActive}
on:detect={(e) => {
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";
});
} else {
state = "error_card_404";
}
});
}
}
}} }}
width={320} type="button"
height={320} 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 w-full mt-2"
class="w-full max-w-sm bg-neutral-300 rounded-lg overflow-hidden" >
/> Next Runner
{#if state === "scan_card"} </button>
{:else if state === "assigning"}
<p>Assigning Card {cardCode}</p>
<p>Please wait a moment while we assign the card...</p>
{:else if state === "error_runner"}
<p>Runner 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 w-full mt-2"
>
Try Again
</button>
{:else if state === "error_card"}
<p>Card not found...</p>
<button
on:click={() => {
state = "scan_card";
scannerActive = true;
}}
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 w-full mt-2"
>
Try Again
</button>
{:else}
<!-- -->
{#if runnerinfo.id === 0}
<h3 class="text-2xl font-bold">Scan Runner</h3>
<h4 class="text-xl font-semibold">
Selfservice QR/ Registration Barcode
</h4>
{:else}
<h3 class="text-2xl font-bold">
{runnerinfo.firstname}
{runnerinfo.lastname}
</h3>
<p>
ID: #{runnerinfo.id}<br />created_via: {runnerinfo.created_via}<br
/>{runnerinfo.group.name}
</p>
{/if}
<!-- -->
{/if}
{#if state === "scan_card"}
<h3 class="text-2xl font-bold">Scan Card</h3>
<h4 class="text-xl font-semibold">Code 128 Barcode</h4>
{/if}
{#if state.includes("scan_")}
{#if scannerActive}
<QrCodeScanner
:paused={!scannerActive}
on:detect={(e) => {
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 {
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}
{#if runnerinfo.id !== 0 && state !== "scan_card"}
<button
on:click={() => {
state = "scan_card";
scannerActive = true;
}}
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 w-full mt-2"
>
Scan Card
</button>
{/if}
{#if state === "scan_card" || runnerinfo.id !== 0}
<button <button
on:click={() => { on:click={() => {
state = "scan_runner"; state = "scan_runner";
scannerActive = true;
runnerinfo = { id: 0, firstname: "", lastname: "" }; runnerinfo = { id: 0, firstname: "", lastname: "" };
cardCode = ""; cardCode = "";
}} }}
@@ -94,30 +190,5 @@
</button> </button>
{/if} {/if}
<!-- --> <!-- -->
{:else}
<!-- -->
{#if state === "assigning"}
<p>Assigning Card {cardCode}</p>
<p>Please wait a moment while we assign the card...</p>
{/if}
{#if state === "done"}
<p>
Assigned Card {cardCode} to {runnerinfo.firstname}
{runnerinfo.lastname} [#{runnerinfo.id}] ✅
</p>
<button
on:click={() => {
// state = "scan_runner";
// runnerinfo = { id: 0, firstname: "", lastname: "" };
// cardCode = "";
location.reload();
}}
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 w-full mt-2"
>
Done
</button>
{/if}
<!-- -->
{/if} {/if}
</div> </div>

View File

@@ -13,9 +13,25 @@
const dispatch = createEventDispatcher(); 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) { function onScanSuccess(decodedText, decodedResult) {
if (!paused) { if (!paused) {
dispatch("detect", { decodedText }); debouncedDispatch(decodedText);
} }
} }
@@ -32,6 +48,7 @@
"qr-scanner", "qr-scanner",
{ {
fps: 10, fps: 10,
showTorchButtonIfSupported: true,
rememberLastUsedCamera: true, rememberLastUsedCamera: true,
qrbox: { width, height }, qrbox: { width, height },
aspectRatio: 1, aspectRatio: 1,
@@ -48,16 +65,6 @@
); );
scanner.render(onScanSuccess, onScanFailure); 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();
}
}
</script> </script>
<div id="qr-scanner" class={$$props.class} /> <div id="qr-scanner" class={$$props.class} />

View File

@@ -92,15 +92,16 @@
return `${group.parentGroup.name} > ${group.name}`; return `${group.parentGroup.name} > ${group.name}`;
}, },
filterFn: `group`, filterFn: `group`,
sortingFn: (rowA, rowB, col)=> { sortingFn: (rowA, rowB, col) => {
return rowA.original.group.name.localeCompare( return rowA.original.group.name.localeCompare(rowB.original.group.name);
rowB.original.group.name,
)
}, },
}, },
{ {
accessorKey: "distance", accessorKey: "distance",
header: () => $_("distance"), header: () => $_("distance"),
sortingFn: (rowA, rowB, col) => {
return rowA.original.distance > rowB.original.distance;
},
cell: (info) => { cell: (info) => {
if (info.getValue() < 1000) { if (info.getValue() < 1000) {
return `${info.getValue()} m`; return `${info.getValue()} m`;