Compare commits

...

18 Commits

Author SHA1 Message Date
06cfd603ca feat(dashboard): Added scanclient tool to dashboard 2025-05-01 20:36:26 +02:00
500886e410 feat(tools): Basic mobile scanner 2025-05-01 20:35:02 +02:00
51d9b35dc4 chore(release): 1.12.8
All checks were successful
Build release images / build-container (push) Successful in 1m2s
2025-05-01 20:04:39 +02:00
16dc789db5 refactor(tools): Move tools to tools route 2025-05-01 19:52:47 +02:00
e4f9b1a605 refactor(tools): Move tools into shared directory instead of the non-descript "general" 2025-05-01 19:47:25 +02:00
3a8533a7ba feat(dasboard): Added section headers to main nav 2025-05-01 19:44:42 +02:00
5ac6fe30b5 fic(locales): Updated dashboard translations 2025-05-01 19:40:37 +02:00
14501d3828 feat(runners): Created_via filters can now be set via query params 2025-05-01 19:38:41 +02:00
c78bdfa5e2 chore(release): 1.12.7
All checks were successful
Build release images / build-container (push) Successful in 1m4s
2025-05-01 19:16:26 +02:00
b2ed2afd8a fix(deps): fresh lockfile 2025-05-01 19:16:03 +02:00
00d198895e refactor(store): update refresh interval from 2min to 60min 2025-05-01 19:10:42 +02:00
b5c079da9a chore(release): 1.12.6
Some checks failed
Build release images / build-container (push) Failing after 7s
2025-05-01 19:09:59 +02:00
93422b9779 feat(pdfs): Experimental generation of large runner card files 2025-05-01 19:08:49 +02:00
6dcfd9a4fe chore(release): 1.12.5
Some checks failed
Build release images / build-container (push) Failing after 6s
2025-05-01 16:20:01 +02:00
6d1919974a chore(deps): Bump @odit/lfk-client-js to 1.2.7 2025-05-01 16:18:57 +02:00
f27c716296 feat(runners): Show total donations in runner detail 2025-05-01 16:18:16 +02:00
21395241de fix(locales): Fixed translation 2025-05-01 16:06:18 +02:00
8d2cb13195 fix: Update release script to include --only-version flag 2025-04-28 21:40:28 +02:00
18 changed files with 2449 additions and 2058 deletions

View File

@@ -2,8 +2,44 @@
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.12.8](https://git.odit.services/lfk/frontend/compare/1.12.7...1.12.8)
- feat(dasboard): Added section headers to main nav [`3a8533a`](https://git.odit.services/lfk/frontend/commit/3a8533a7baef02f7bc9780ce37be1a350bd92270)
- fic(locales): Updated dashboard translations [`5ac6fe3`](https://git.odit.services/lfk/frontend/commit/5ac6fe30b5b9e34043c734d51d5da137fdf7ac38)
- feat(runners): Created_via filters can now be set via query params [`14501d3`](https://git.odit.services/lfk/frontend/commit/14501d3828dd0d48ba0baeeddf936ba275f7b9b7)
- refactor(tools): Move tools to tools route [`16dc789`](https://git.odit.services/lfk/frontend/commit/16dc789db5d9ea41774c77622a579cc0d9bd95f2)
- refactor(tools): Move tools into shared directory instead of the non-descript "general" [`e4f9b1a`](https://git.odit.services/lfk/frontend/commit/e4f9b1a60551d7955def4d068d534cf17b1ea640)
#### [1.12.7](https://git.odit.services/lfk/frontend/compare/1.12.6...1.12.7)
> 1 May 2025
- chore(release): 1.12.7 [`c78bdfa`](https://git.odit.services/lfk/frontend/commit/c78bdfa5e24ada4909455064dd6b05cf34fc6df3)
- fix(deps): fresh lockfile [`b2ed2af`](https://git.odit.services/lfk/frontend/commit/b2ed2afd8a45a1a01ac6118b27941e3b4b3b611f)
- refactor(store): update refresh interval from 2min to 60min [`00d1988`](https://git.odit.services/lfk/frontend/commit/00d198895e15174b70a8d229974b4baa7d0ed8fc)
#### [1.12.6](https://git.odit.services/lfk/frontend/compare/1.12.5...1.12.6)
> 1 May 2025
- feat(pdfs): Experimental generation of large runner card files [`93422b9`](https://git.odit.services/lfk/frontend/commit/93422b97799c5e45c89acadd34f33b1a11b04617)
- chore(release): 1.12.6 [`b5c079d`](https://git.odit.services/lfk/frontend/commit/b5c079da9a0545d146e9f3029a543e04c907add3)
#### [1.12.5](https://git.odit.services/lfk/frontend/compare/1.12.4...1.12.5)
> 1 May 2025
- chore(release): 1.12.5 [`6dcfd9a`](https://git.odit.services/lfk/frontend/commit/6dcfd9a4fedd1e44894c9803482576bc650fb4db)
- fix(locales): Fixed translation [`2139524`](https://git.odit.services/lfk/frontend/commit/21395241de4de8f3a6b8404758d09c01d8a6f95f)
- feat(runners): Show total donations in runner detail [`f27c716`](https://git.odit.services/lfk/frontend/commit/f27c716296e228ecccbf500a21130f1bc47ea52d)
- chore(deps): Bump @odit/lfk-client-js to 1.2.7 [`6d19199`](https://git.odit.services/lfk/frontend/commit/6d1919974aacd74a265cf9ce0c9ed501028f0aa3)
- fix: Update release script to include --only-version flag [`8d2cb13`](https://git.odit.services/lfk/frontend/commit/8d2cb13195856f47022d414f3243e9a21457832b)
#### [1.12.4](https://git.odit.services/lfk/frontend/compare/1.12.3...1.12.4) #### [1.12.4](https://git.odit.services/lfk/frontend/compare/1.12.3...1.12.4)
> 28 April 2025
- chore(release): 1.12.4 [`e61e8b0`](https://git.odit.services/lfk/frontend/commit/e61e8b063af75539b7db93c5ca42965417019f29)
- fix: Disable ios auto zooming on inputs [`073c78d`](https://git.odit.services/lfk/frontend/commit/073c78d98afd1c2f08b190aeda942a634e9bb888) - fix: Disable ios auto zooming on inputs [`073c78d`](https://git.odit.services/lfk/frontend/commit/073c78d98afd1c2f08b190aeda942a634e9bb888)
#### [1.12.3](https://git.odit.services/lfk/frontend/compare/1.12.2...1.12.3) #### [1.12.3](https://git.odit.services/lfk/frontend/compare/1.12.2...1.12.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.12.4-RELEASE_INFO</span >RELEASE_INFO-1.12.8-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,13 +1,13 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "1.12.4", "version": "1.12.8",
"type": "module", "type": "module",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
"dev": "vite", "dev": "vite",
"format": "prettier --write --plugin-search-dir=. .", "format": "prettier --write --plugin-search-dir=. .",
"build": "vite build", "build": "vite build",
"release": "release-it", "release": "release-it --only-version",
"licenses:export": "license-exporter --json -o public" "licenses:export": "license-exporter --json -o public"
}, },
"license": "CC-BY-NC-SA-4.0", "license": "CC-BY-NC-SA-4.0",
@@ -43,7 +43,7 @@
"dependencies": { "dependencies": {
"@bwip-js/browser": "^4.6.0", "@bwip-js/browser": "^4.6.0",
"@fontsource/athiti": "^5.2.5", "@fontsource/athiti": "^5.2.5",
"@odit/lfk-client-js": "1.2.5", "@odit/lfk-client-js": "1.2.7",
"@paralleldrive/cuid2": "2.2.2", "@paralleldrive/cuid2": "2.2.2",
"@tailwindcss/vite": "^4.1.4", "@tailwindcss/vite": "^4.1.4",
"@tanstack/svelte-table": "8.9.1", "@tanstack/svelte-table": "8.9.1",

10
pnpm-lock.yaml generated
View File

@@ -15,8 +15,8 @@ importers:
specifier: ^5.2.5 specifier: ^5.2.5
version: 5.2.5 version: 5.2.5
'@odit/lfk-client-js': '@odit/lfk-client-js':
specifier: 1.2.5 specifier: 1.2.7
version: 1.2.5 version: 1.2.7
'@paralleldrive/cuid2': '@paralleldrive/cuid2':
specifier: 2.2.2 specifier: 2.2.2
version: 2.2.2 version: 2.2.2
@@ -491,8 +491,8 @@ packages:
'@octokit/types@13.10.0': '@octokit/types@13.10.0':
resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==}
'@odit/lfk-client-js@1.2.5': '@odit/lfk-client-js@1.2.7':
resolution: {integrity: sha512-a5vwqpjFXB5cVOCmjC/tZVi9OXJS8aMesNidSqwK2cwA/oC5yTJAqxKXGDhq9k/JLLipVGDJdaKMYmYVzRWkgA==} resolution: {integrity: sha512-sqbbTjGlalN32VPshXClR3qM0+TFgWCX9+2UCo7u/tABEIs7hsYTVXKSZ+fJNfAUCK6ZJiZV0ND6+Dcnk7s29A==}
'@odit/license-exporter@0.2.0': '@odit/license-exporter@0.2.0':
resolution: {integrity: sha512-RRyfQzDLoyLQlGSd8ThJQ3h0fiCe4tkmm935AUvSVQWP+p88FcnI4iaktKBJJVBnIpDhkv/7sDSA5dFc/QMM5w==} resolution: {integrity: sha512-RRyfQzDLoyLQlGSd8ThJQ3h0fiCe4tkmm935AUvSVQWP+p88FcnI4iaktKBJJVBnIpDhkv/7sDSA5dFc/QMM5w==}
@@ -2412,7 +2412,7 @@ snapshots:
dependencies: dependencies:
'@octokit/openapi-types': 24.2.0 '@octokit/openapi-types': 24.2.0
'@odit/lfk-client-js@1.2.5': {} '@odit/lfk-client-js@1.2.7': {}
'@odit/license-exporter@0.2.0': '@odit/license-exporter@0.2.0':
dependencies: dependencies:

BIN
public/error.mp3 Normal file

Binary file not shown.

View File

@@ -41,7 +41,7 @@
import Settings from "./components/settings/Settings.svelte"; import Settings from "./components/settings/Settings.svelte";
import Transition from "./components/base/Transition.svelte"; import Transition from "./components/base/Transition.svelte";
import Orgs from "./components/orgs/Orgs.svelte"; import Orgs from "./components/orgs/Orgs.svelte";
import CardAssignment from "./components/general/CardAssignment.svelte"; import CardAssignment from "./components/tools/CardAssignment.svelte";
import Runners from "./components/runners/Runners.svelte"; import Runners from "./components/runners/Runners.svelte";
import Footer from "./components/general/Footer.svelte"; import Footer from "./components/general/Footer.svelte";
import TracksOverview from "./components/tracks/TracksOverview.svelte"; import TracksOverview from "./components/tracks/TracksOverview.svelte";
@@ -70,7 +70,8 @@
import Cards from "./components/cards/Cards.svelte"; import Cards from "./components/cards/Cards.svelte";
import StatsClients from "./components/statsclients/StatsClients.svelte"; import StatsClients from "./components/statsclients/StatsClients.svelte";
import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte"; import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte";
import CardReplacement from "./components/general/CardReplacement.svelte"; import CardReplacement from "./components/tools/CardReplacement.svelte";
import ScanClient from "./components/tools/ScanClient.svelte";
store.init(); store.init();
</script> </script>
@@ -126,22 +127,23 @@
<Route path="/:trackid" let:params /> <Route path="/:trackid" let:params />
</Route> </Route>
<Route path="/runners/*"> <Route path="/runners/*">
<Route path="/"> <Route path="/" let:meta>
<Runners created_via="all" /> <Runners created_via={meta.query.created_via} />
</Route> </Route>
<Route path="/:runnerid" let:params> <Route path="/:runnerid" let:params>
<RunnerDetail {params} /> <RunnerDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/cardassignment/*"> <Route path="/tools/*">
<Route path="/"> <Route path="/cardassignment/">
<CardAssignment /> <CardAssignment />
</Route> </Route>
</Route> <Route path="/cardreplacement/">
<Route path="/cardreplacement/*">
<Route path="/">
<CardReplacement /> <CardReplacement />
</Route> </Route>
<Route path="/scanclient/">
<ScanClient />
</Route>
</Route> </Route>
<Route path="/teams/*"> <Route path="/teams/*">
<Route path="/"> <Route path="/">

View File

@@ -41,11 +41,14 @@
</svg> </svg>
<span>{$_("dashboard-title")}</span> <span>{$_("dashboard-title")}</span>
</a> </a>
<h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase">
{$_("quick-tools")}
</h2>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET") && store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET") && store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
<a <a
class:activenav={$router.path.includes("/cardassignment/")} class:activenav={$router.path.includes("/tools/cardassignment/")}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
href="/cardassignment/" href="/tools/cardassignment/"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -60,12 +63,12 @@
/> />
</svg> </svg>
<span>{$_('card_assignment_menu')}</span> <span>{$_("card_assignment_menu")}</span>
</a> </a>
<a <a
class:activenav={$router.path.includes("/cardreplacement/")} class:activenav={$router.path.includes("/tools/cardreplacement/")}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
href="/cardreplacement/" href="/tools/cardreplacement/"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -80,8 +83,31 @@
/> />
</svg> </svg>
<span>{$_('card-replacement-menu')}</span> <span>{$_("card-replacement-menu")}</span>
</a> </a>
<a
class:activenav={$router.path.includes("/tools/scanclient/")}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
href="/tools/scanclient/"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
>
<path
fill-rule="evenodd"
d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z"
clip-rule="evenodd"
/>
</svg>
<span>{$_("scanclient")}</span>
</a>
<h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase">
{$_("management")}
</h2>
<a <a
class:activenav={$router.path.includes("/runners/")} class:activenav={$router.path.includes("/runners/")}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
@@ -359,6 +385,9 @@
<span>{$_("user-groups")}</span> <span>{$_("user-groups")}</span>
</a> </a>
{/if} {/if}
<h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase">
{$_("system")}
</h2>
<a <a
class:activenav={$router.path === "/settings/"} class:activenav={$router.path === "/settings/"}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"

View File

@@ -220,7 +220,7 @@
<StatCard <StatCard
title={$_("runner_via_selfservice")} title={$_("runner_via_selfservice")}
value={stats.runnersViaSelfservice} value={stats.runnersViaSelfservice}
href="/runners/" href="/runners/?created_via=selfservice"
> >
<svg <svg
height="24" height="24"
@@ -237,7 +237,7 @@
<StatCard <StatCard
title={$_('runners_via_kiosk')} title={$_('runners_via_kiosk')}
value={stats.runnersViaKiosk} value={stats.runnersViaKiosk}
href="/runners/" href="/runners/?created_via=kiosk"
> >
<svg <svg
height="24" height="24"

View File

@@ -33,9 +33,13 @@
toast.success($_("pdf-successfully-generated")); toast.success($_("pdf-successfully-generated"));
} }
function generateRunnerCards(locale) { function generateRunnerCards(locale, useCombined = false) {
if (generate_orgs.length > 0) { if (generate_orgs.length > 0) {
generateOrgCards(locale); if(useCombined){
generateOrgCardsCombined(locale);
} else {
generateOrgCards(locale)
}
} else if (generate_teams.length > 0) { } else if (generate_teams.length > 0) {
generateTeamCards(locale); generateTeamCards(locale);
} else if (generate_runners.length > 0) { } else if (generate_runners.length > 0) {
@@ -175,6 +179,55 @@
} }
} }
} }
async function generateOrgCardsCombined(locale) {
toast.loading($_("generating-pdfs"));
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let count = 0;
let count_orgs = 0;
for (const o of generate_orgs) {
count_orgs++;
let cards = [];
let count = 0;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) {
card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id,
});
}
cards.push(card);
}
}
await documentServer
.generateCards(cards, locale)
.then((blob) => {
download(
blob,
`${$_("runnercards")}_${o.name}-${locale}-${createId()}.pdf`
);
})
.catch((err) => {});
}
}
</script> </script>
{#if cards_show} {#if cards_show}
@@ -182,6 +235,9 @@
on:click={() => { on:click={() => {
generateRunnerCards("de"); generateRunnerCards("de");
}} }}
on:contextmenu|preventDefault={() => {
generateRunnerCards("de", true);
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0" class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
> >
{$_("generate-runnercards")}: DE {$_("generate-runnercards")}: DE
@@ -189,6 +245,9 @@
<button <button
on:click={() => { on:click={() => {
generateRunnerCards("en"); generateRunnerCards("en");
}}
on:contextmenu|preventDefault={() => {
generateRunnerCards("en", true);
}} }}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0" class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0"
> >

View File

@@ -289,6 +289,13 @@
<br /> <br />
<span class="text-gray-700">{original_data.distance / 1000} km</span> <span class="text-gray-700">{original_data.distance / 1000} km</span>
</div> </div>
<div class="text-sm w-full mt-2">
<span class="font-semibold text-gray-700">{$_("total-donation-amount")}</span>
<br />
<span class="text-gray-700">{(editable.donationAmount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</span>
</div>
<div class="text-sm w-full mt-2"> <div class="text-sm w-full mt-2">
<span class="font-semibold text-gray-700">{$_("created_via")}</span> <span class="font-semibold text-gray-700">{$_("created_via")}</span>
<br /> <br />

View File

@@ -176,7 +176,6 @@
const runners = await RunnerService.runnerControllerGetAll( const runners = await RunnerService.runnerControllerGetAll(
page, page,
500, 500,
created_via
); );
if (runners.length == 0) { if (runners.length == 0) {
page = -2; page = -2;
@@ -200,15 +199,21 @@
$: current_runners = []; $: current_runners = [];
export let modal_open = false; export let modal_open = false;
export let import_modal_open = false; export let import_modal_open = false;
if (created_via != "all") {
$table.setColumnFilters([
{
id: "created_via",
value: created_via,
},
]);
}
</script> </script>
<section class="container p-5"> <section class="container p-5">
<h4 class="mb-1 text-3xl font-extrabold leading-tight"> <h4 class="mb-1 text-3xl font-extrabold leading-tight">
{$_("runners")} {$_("runners")}
</h4> </h4>
{#if created_via !== "all"}
<p>created_via={created_via}</p>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<button <button
on:click={() => { on:click={() => {

View 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>

View File

@@ -530,5 +530,10 @@
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben", "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-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.", "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",
"scanclient": "Scanclient"
} }

View File

@@ -51,7 +51,7 @@
"author": "Author", "author": "Author",
"available-permissions": "available", "available-permissions": "available",
"average-distance": "∅ distance", "average-distance": "∅ distance",
"average-donation": "∅ donation", "average-donation": "∅ Donation",
"barcode_scanner": "Scan via barcode scanner", "barcode_scanner": "Scan via barcode scanner",
"by": "by", "by": "by",
"cancel": "Cancel", "cancel": "Cancel",
@@ -388,7 +388,7 @@
"runner-is-being-added": "Runner is being added...", "runner-is-being-added": "Runner is being added...",
"runner-updated": "Runner updated!", "runner-updated": "Runner updated!",
"runner_not_found": "Runner not found...", "runner_not_found": "Runner not found...",
"runner_via_selfservice": "Runner via Selfservice", "runner_via_selfservice": "Runners via Selfservice",
"runnercards": "Runnercards", "runnercards": "Runnercards",
"runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"", "runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"",
"runners": "Runners", "runners": "Runners",
@@ -468,12 +468,12 @@
"timestamp": "timestamp", "timestamp": "timestamp",
"token": "Token", "token": "Token",
"total-distance": "total distance", "total-distance": "total distance",
"total-donation-amount": "total donation amount", "total-donation-amount": "Total donations",
"total-donation-count": "total donations (count)", "total-donation-count": "Donations (count)",
"total-donations": "total donations", "total-donations": "Donations (amount)",
"total-donors": "total donors", "total-donors": "Donors",
"total-paid-amount": "Paid", "total-paid-amount": "Paid",
"total-scans": "total scans", "total-scans": "Scans",
"total_donation_amount_in_eur": "Total donation amount in €", "total_donation_amount_in_eur": "Total donation amount in €",
"track": "Track", "track": "Track",
"track-added": "Track added", "track-added": "Track added",
@@ -530,5 +530,9 @@
"you-have-to-provide-an-organization": "You have to provide an organization", "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-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.", "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",
"scanclient": "Scanclient"
} }

View File

@@ -43,8 +43,8 @@ const store = () => {
// //
state.refreshInterval = setInterval(() => { state.refreshInterval = setInterval(() => {
this.refreshAuth(); this.refreshAuth();
// 2min // 60min
}, 2 * 60000); }, 60 * 60000);
// //
return state; return state;
}); });