Compare commits

..

19 Commits

Author SHA1 Message Date
f5df252857 chore(release): 1.13.1
All checks were successful
Build release images / build-container (push) Successful in 1m7s
2025-05-16 16:42:42 +02:00
285fc91c66 feat(tools): Remove unnecessary validation display in donation creation 2025-05-16 16:42:09 +02:00
d95b6cf589 chore(release): 1.13.0
All checks were successful
Build release images / build-container (push) Successful in 1m9s
2025-05-16 16:41:21 +02:00
51ba1c852c feat(tools): Added tool for fast sponsoring creation 2025-05-16 16:40:58 +02:00
80ca7aa08b feat(tools): Remove requirement for ten-diget codes 2025-05-01 20:51:01 +02:00
25c38ea381 feat(dev): Enable devserver with https-support to circumvent ios https requirements for camera access 2025-05-01 20:50:36 +02:00
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
19 changed files with 1907 additions and 1000 deletions

View File

@@ -2,8 +2,52 @@
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.13.1](https://git.odit.services/lfk/frontend/compare/1.13.0...1.13.1)
- feat(tools): Remove unnecessary validation display in donation creation [`285fc91`](https://git.odit.services/lfk/frontend/commit/285fc91c66d03aaa861da549cb739c3698beb892)
#### [1.13.0](https://git.odit.services/lfk/frontend/compare/1.12.8...1.13.0)
> 16 May 2025
- feat(tools): Basic mobile scanner [`500886e`](https://git.odit.services/lfk/frontend/commit/500886e4106f4b53fbc40fb0fa15653f574c8328)
- chore(release): 1.13.0 [`d95b6cf`](https://git.odit.services/lfk/frontend/commit/d95b6cf5894dd0b487353f36c9f3a436066fd4ef)
- feat(tools): Added tool for fast sponsoring creation [`51ba1c8`](https://git.odit.services/lfk/frontend/commit/51ba1c852cad6243e935409da1eacecc5dcfa5fa)
- feat(dev): Enable devserver with https-support to circumvent ios https requirements for camera access [`25c38ea`](https://git.odit.services/lfk/frontend/commit/25c38ea3812a529a90294ff8834bdb65c487f8c4)
- feat(tools): Remove requirement for ten-diget codes [`80ca7aa`](https://git.odit.services/lfk/frontend/commit/80ca7aa08bdd44591e2d3efaa8e59dd4db5c864e)
- feat(dashboard): Added scanclient tool to dashboard [`06cfd60`](https://git.odit.services/lfk/frontend/commit/06cfd603cae79e0237bbece43203083f198d03a1)
#### [1.12.8](https://git.odit.services/lfk/frontend/compare/1.12.7...1.12.8)
> 1 May 2025
- chore(release): 1.12.8 [`51d9b35`](https://git.odit.services/lfk/frontend/commit/51d9b35dc41fea0d0245fd136556f9fada3559da)
- 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.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) - 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) - 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) - chore(deps): Bump @odit/lfk-client-js to 1.2.7 [`6d19199`](https://git.odit.services/lfk/frontend/commit/6d1919974aacd74a265cf9ce0c9ed501028f0aa3)

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.5-RELEASE_INFO</span >RELEASE_INFO-1.13.1-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.12.5", "version": "1.13.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
@@ -21,7 +21,8 @@
"prettier-plugin-svelte": "3.3.3", "prettier-plugin-svelte": "3.3.3",
"release-it": "17.10.0", "release-it": "17.10.0",
"svelte-select": "3.17.0", "svelte-select": "3.17.0",
"vite": "6.3.2" "vite": "6.3.2",
"vite-plugin-mkcert": "^1.17.8"
}, },
"release-it": { "release-it": {
"git": { "git": {
@@ -51,6 +52,7 @@
"html5-qrcode": "^2.3.8", "html5-qrcode": "^2.3.8",
"localforage": "1.10.0", "localforage": "1.10.0",
"papaparse": "^5.5.2", "papaparse": "^5.5.2",
"svelecte": "3",
"svelte": "3.58.0", "svelte": "3.58.0",
"svelte-french-toast": "1.2.0", "svelte-french-toast": "1.2.0",
"svelte-i18n": "4.0.1", "svelte-i18n": "4.0.1",

199
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
@@ -38,6 +38,9 @@ importers:
papaparse: papaparse:
specifier: ^5.5.2 specifier: ^5.5.2
version: 5.5.2 version: 5.5.2
svelecte:
specifier: '3'
version: 3.17.3
svelte: svelte:
specifier: 3.58.0 specifier: 3.58.0
version: 3.58.0 version: 3.58.0
@@ -93,6 +96,9 @@ importers:
vite: vite:
specifier: 6.3.2 specifier: 6.3.2
version: 6.3.2(@types/node@22.15.2)(jiti@2.4.2)(lightningcss@1.29.2) version: 6.3.2(@types/node@22.15.2)(jiti@2.4.2)(lightningcss@1.29.2)
vite-plugin-mkcert:
specifier: ^1.17.8
version: 1.17.8(vite@6.3.2(@types/node@22.15.2)(jiti@2.4.2)(lightningcss@1.29.2))
packages: packages:
@@ -491,8 +497,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==}
@@ -780,6 +786,9 @@ packages:
async-retry@1.3.3: async-retry@1.3.3:
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
atomically@2.0.3: atomically@2.0.3:
resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==} resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==}
@@ -788,6 +797,9 @@ packages:
engines: {node: '>=8.3'} engines: {node: '>=8.3'}
hasBin: true hasBin: true
axios@1.9.0:
resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==}
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -822,6 +834,10 @@ packages:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'} engines: {node: '>=18'}
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
callsites@3.1.0: callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -895,6 +911,10 @@ packages:
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
commander@7.2.0: commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@@ -974,6 +994,10 @@ packages:
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
deprecation@2.3.1: deprecation@2.3.1:
resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==}
@@ -985,6 +1009,10 @@ packages:
resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
emoji-regex@10.4.0: emoji-regex@10.4.0:
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
@@ -1002,6 +1030,22 @@ packages:
error-ex@1.3.2: error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
es-set-tostringtag@2.1.0:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
es5-ext@0.10.64: es5-ext@0.10.64:
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@@ -1096,6 +1140,19 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'} engines: {node: '>=8'}
follow-redirects@1.15.9:
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
form-data@4.0.2:
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
engines: {node: '>= 6'}
frac@1.1.2: frac@1.1.2:
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
@@ -1119,6 +1176,14 @@ packages:
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
get-stream@6.0.1: get-stream@6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1159,6 +1224,10 @@ packages:
globrex@0.1.2: globrex@0.1.2:
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
graceful-fs@4.2.10: graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
@@ -1174,6 +1243,14 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hasown@2.0.2: hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1489,6 +1566,10 @@ packages:
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
memoizee@0.4.17: memoizee@0.4.17:
resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@@ -1905,6 +1986,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
svelecte@3.17.3:
resolution: {integrity: sha512-wnvoRxJIFFkm+CmXgjL4R3i/TcuYUIBkE+jDJSBD7AdSOzk1K6u3+nW4zwxaGT29zyZpiZkWeiy7lO62r5F+tg==}
svelte-french-toast@1.2.0: svelte-french-toast@1.2.0:
resolution: {integrity: sha512-5PW+6RFX3xQPbR44CngYAP1Sd9oCq9P2FOox4FZffzJuZI2mHOB7q5gJBVnOiLF5y3moVGZ7u2bYt7+yPAgcEQ==} resolution: {integrity: sha512-5PW+6RFX3xQPbR44CngYAP1Sd9oCq9P2FOox4FZffzJuZI2mHOB7q5gJBVnOiLF5y3moVGZ7u2bYt7+yPAgcEQ==}
peerDependencies: peerDependencies:
@@ -1926,6 +2010,9 @@ packages:
svelte-select@3.17.0: svelte-select@3.17.0:
resolution: {integrity: sha512-ITmX/XUiSdkaILmsTviKRkZPaXckM5/FA7Y8BhiUPoamaZG/ZDyOo6ydjFu9fDVFTbwoAUGUi6HBjs+ZdK2AwA==} resolution: {integrity: sha512-ITmX/XUiSdkaILmsTviKRkZPaXckM5/FA7Y8BhiUPoamaZG/ZDyOo6ydjFu9fDVFTbwoAUGUi6HBjs+ZdK2AwA==}
svelte-tiny-virtual-list@2.1.2:
resolution: {integrity: sha512-jeP/WMvgFUR4mYXHGPiCexjX5DuzSO+3xzHNhxfcsFyy+uYPtnqI5UGb383swpzQAyXB0OBqYfzpYihD/5gxnA==}
svelte-writable-derived@3.1.1: svelte-writable-derived@3.1.1:
resolution: {integrity: sha512-w4LR6/bYZEuCs7SGr+M54oipk/UQKtiMadyOhW0PTwAtJ/Ai12QS77sLngEcfBx2q4H8ZBQucc9ktSA5sUGZWw==} resolution: {integrity: sha512-w4LR6/bYZEuCs7SGr+M54oipk/UQKtiMadyOhW0PTwAtJ/Ai12QS77sLngEcfBx2q4H8ZBQucc9ktSA5sUGZWw==}
peerDependencies: peerDependencies:
@@ -2018,6 +2105,12 @@ packages:
resolution: {integrity: sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==} resolution: {integrity: sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
vite-plugin-mkcert@1.17.8:
resolution: {integrity: sha512-S+4tNEyGqdZQ3RLAG54ETeO2qyURHWrVjUWKYikLAbmhh/iJ+36gDEja4OWwFyXNuvyXcZwNt5TZZR9itPeG5Q==}
engines: {node: '>=v16.7.0'}
peerDependencies:
vite: '>=3'
vite@6.3.2: vite@6.3.2:
resolution: {integrity: sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==} resolution: {integrity: sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -2412,7 +2505,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:
@@ -2627,6 +2720,8 @@ snapshots:
dependencies: dependencies:
retry: 0.13.1 retry: 0.13.1
asynckit@0.4.0: {}
atomically@2.0.3: atomically@2.0.3:
dependencies: dependencies:
stubborn-fs: 1.2.5 stubborn-fs: 1.2.5
@@ -2643,6 +2738,14 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
axios@1.9.0(debug@4.4.0):
dependencies:
follow-redirects: 1.15.9(debug@4.4.0)
form-data: 4.0.2
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
base64-js@1.5.1: {} base64-js@1.5.1: {}
@@ -2686,6 +2789,11 @@ snapshots:
dependencies: dependencies:
run-applescript: 7.0.0 run-applescript: 7.0.0
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
callsites@3.1.0: {} callsites@3.1.0: {}
camelcase@8.0.0: {} camelcase@8.0.0: {}
@@ -2746,6 +2854,10 @@ snapshots:
color-name@1.1.4: {} color-name@1.1.4: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
commander@7.2.0: {} commander@7.2.0: {}
concat-map@0.0.1: {} concat-map@0.0.1: {}
@@ -2813,6 +2925,8 @@ snapshots:
escodegen: 2.1.0 escodegen: 2.1.0
esprima: 4.0.1 esprima: 4.0.1
delayed-stream@1.0.0: {}
deprecation@2.3.1: {} deprecation@2.3.1: {}
detect-libc@2.0.4: {} detect-libc@2.0.4: {}
@@ -2821,6 +2935,12 @@ snapshots:
dependencies: dependencies:
type-fest: 4.40.0 type-fest: 4.40.0
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
es-errors: 1.3.0
gopd: 1.2.0
emoji-regex@10.4.0: {} emoji-regex@10.4.0: {}
emoji-regex@8.0.0: {} emoji-regex@8.0.0: {}
@@ -2836,6 +2956,21 @@ snapshots:
dependencies: dependencies:
is-arrayish: 0.2.1 is-arrayish: 0.2.1
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
es-set-tostringtag@2.1.0:
dependencies:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.2
es5-ext@0.10.64: es5-ext@0.10.64:
dependencies: dependencies:
es6-iterator: 2.0.3 es6-iterator: 2.0.3
@@ -3001,6 +3136,17 @@ snapshots:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
follow-redirects@1.15.9(debug@4.4.0):
optionalDependencies:
debug: 4.4.0
form-data@4.0.2:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
mime-types: 2.1.35
frac@1.1.2: {} frac@1.1.2: {}
fs.realpath@1.0.0: {} fs.realpath@1.0.0: {}
@@ -3014,6 +3160,24 @@ snapshots:
get-east-asian-width@1.3.0: {} get-east-asian-width@1.3.0: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
get-stream@6.0.1: {} get-stream@6.0.1: {}
get-stream@8.0.1: {} get-stream@8.0.1: {}
@@ -3065,6 +3229,8 @@ snapshots:
globrex@0.1.2: {} globrex@0.1.2: {}
gopd@1.2.0: {}
graceful-fs@4.2.10: {} graceful-fs@4.2.10: {}
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
@@ -3080,6 +3246,12 @@ snapshots:
has-flag@4.0.0: {} has-flag@4.0.0: {}
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
dependencies:
has-symbols: 1.1.0
hasown@2.0.2: hasown@2.0.2:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
@@ -3343,6 +3515,8 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
math-intrinsics@1.1.0: {}
memoizee@0.4.17: memoizee@0.4.17:
dependencies: dependencies:
d: 1.0.2 d: 1.0.2
@@ -3781,6 +3955,10 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {} supports-preserve-symlinks-flag@1.0.0: {}
svelecte@3.17.3:
dependencies:
svelte-tiny-virtual-list: 2.1.2
svelte-french-toast@1.2.0(svelte@3.58.0): svelte-french-toast@1.2.0(svelte@3.58.0):
dependencies: dependencies:
svelte: 3.58.0 svelte: 3.58.0
@@ -3803,6 +3981,8 @@ snapshots:
svelte-select@3.17.0: {} svelte-select@3.17.0: {}
svelte-tiny-virtual-list@2.1.2: {}
svelte-writable-derived@3.1.1(svelte@3.58.0): svelte-writable-derived@3.1.1(svelte@3.58.0):
dependencies: dependencies:
svelte: 3.58.0 svelte: 3.58.0
@@ -3880,6 +4060,15 @@ snapshots:
validator@13.15.0: {} validator@13.15.0: {}
vite-plugin-mkcert@1.17.8(vite@6.3.2(@types/node@22.15.2)(jiti@2.4.2)(lightningcss@1.29.2)):
dependencies:
axios: 1.9.0(debug@4.4.0)
debug: 4.4.0
picocolors: 1.1.1
vite: 6.3.2(@types/node@22.15.2)(jiti@2.4.2)(lightningcss@1.29.2)
transitivePeerDependencies:
- supports-color
vite@6.3.2(@types/node@22.15.2)(jiti@2.4.2)(lightningcss@1.29.2): vite@6.3.2(@types/node@22.15.2)(jiti@2.4.2)(lightningcss@1.29.2):
dependencies: dependencies:
esbuild: 0.25.3 esbuild: 0.25.3

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,9 @@
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";
import DonationCreate from "./components/tools/DonationCreate.svelte";
store.init(); store.init();
</script> </script>
@@ -126,21 +128,25 @@
<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/*"> <CardReplacement />
<Route path="/"> </Route>
<CardReplacement /> <Route path="/scanclient/">
<ScanClient />
</Route>
<Route path="/donationcreate/">
<DonationCreate />
</Route> </Route>
</Route> </Route>
<Route path="/teams/*"> <Route path="/teams/*">

View File

@@ -1,479 +1,528 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import localForage from "localforage"; import localForage from "localforage";
import store from "../../store"; import store from "../../store";
import { router } from "tinro"; import { router } from "tinro";
import NoComponentLoaded from "../base/NoComponentLoaded.svelte"; import NoComponentLoaded from "../base/NoComponentLoaded.svelte";
import { AuthService } from "@odit/lfk-client-js"; import { AuthService } from "@odit/lfk-client-js";
import { Toaster } from "svelte-french-toast"; import { Toaster } from "svelte-french-toast";
$: navOpen = false; $: navOpen = false;
function logout() { function logout() {
localForage.clear(); localForage.clear();
location.replace("/"); location.replace("/");
} }
</script> </script>
<section class="min-h-screen bg-gray-50"> <section class="min-h-screen bg-gray-50">
<div <div
class:collapsed_navigation={!navOpen} class:collapsed_navigation={!navOpen}
style="z-index:11;" style="z-index:11;"
class="select-none fixed top-0 left-0 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 bg-gray-50" class="select-none fixed top-0 left-0 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 bg-gray-50"
> >
<a href="/" class="flex items-center px-4 py-5"> <a href="/" class="flex items-center px-4 py-5">
<img src="/lfk-logo.png" alt="Logo" class="h-10" /> <img src="/lfk-logo.png" alt="Logo" class="h-10" />
<h3 class="text-lg font-bold">LfK!Admin</h3> <h3 class="text-lg font-bold">LfK!Admin</h3>
</a> </a>
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation"> <nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
<a <a
class:activenav={$router.path === "/"} class:activenav={$router.path === "/"}
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="/" href="/"
> >
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" viewBox="0 0 20 20"
fill="currentColor" fill="currentColor"
> >
<path <path
d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"
/> />
</svg> </svg>
<span>{$_("dashboard-title")}</span> <span>{$_("dashboard-title")}</span>
</a> </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET") && store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase">
<a {$_("quick-tools")}
class:activenav={$router.path.includes("/cardassignment/")} </h2>
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET") && store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
href="/cardassignment/" <a
> class:activenav={$router.path.includes("/tools/cardassignment/")}
<svg class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
xmlns="http://www.w3.org/2000/svg" href="/tools/cardassignment/"
viewBox="0 0 24 24" >
fill="currentColor" <svg
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" xmlns="http://www.w3.org/2000/svg"
> viewBox="0 0 24 24"
<path fill="currentColor"
fill-rule="evenodd" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
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" <path
/> fill-rule="evenodd"
</svg> 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>{$_('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"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
> >
<path <path
fill-rule="evenodd" 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" 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" clip-rule="evenodd"
/> />
</svg> </svg>
<span>{$_('card-replacement-menu')}</span> <span>{$_("card-replacement-menu")}</span>
</a> </a>
<a <a
class:activenav={$router.path.includes("/runners/")} 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" 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="/runners/" href="/tools/scanclient/"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" fill="currentColor"
fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
width="24" >
height="24" <path
><path fill="none" d="M0 0h24v24H0z" /> fill-rule="evenodd"
<path 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"
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" clip-rule="evenodd"
/></svg />
> </svg>
<span>{$_("runners")}</span>
</a> <span>{$_("scanclient")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:GET")} <a
<a class:activenav={$router.path.includes("/tools/donationcreate/")}
class:activenav={$router.path.includes("/teams/")} 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="/tools/donationcreate/"
href="/teams/" >
> <svg
<svg xmlns="http://www.w3.org/2000/svg"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
width="24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
height="24" >
xmlns="http://www.w3.org/2000/svg" <path
viewBox="0 0 640 512" fill-rule="evenodd"
><path 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"
fill="currentColor" clip-rule="evenodd"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" />
/></svg </svg>
>
<span>{$_("teams")}</span> <span>{$_("donation-quick-add")}</span>
</a> </a>
{/if} <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase">
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:GET")} {$_("management")}
<a </h2>
class:activenav={$router.path.includes("/orgs/")} <a
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:activenav={$router.path.includes("/runners/")}
href="/orgs/" 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="/runners/"
<svg >
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" <svg
fill="currentColor" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
viewBox="0 0 24 24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
width="24" fill="currentColor"
height="24" width="24"
><path fill="none" d="M0 0h24v24H0z" /> height="24"
<path ><path fill="none" d="M0 0h24v24H0z" />
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" <path
/></svg d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"
> /></svg
<span>{$_("orgs")}</span> >
</a> <span>{$_("runners")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:GET")}
class:activenav={$router.path.includes("/donors/")} <a
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:activenav={$router.path.includes("/teams/")}
href="/donors/" 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="/teams/"
<svg >
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" <svg
fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 24 24" width="24"
width="24" height="24"
height="24" xmlns="http://www.w3.org/2000/svg"
><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 640 512"
<path ><path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" fill="currentColor"
/></svg d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"
> /></svg
<span>{$_("donors")}</span> >
</a> <span>{$_("teams")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:GET")}
class:activenav={$router.path.includes("/donations/")} <a
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:activenav={$router.path.includes("/orgs/")}
href="/donations/" 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="/orgs/"
<svg >
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" <svg
fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
width="24" viewBox="0 0 24 24"
height="24" width="24"
><path fill="none" d="M0 0h24v24H0z" /> height="24"
<path ><path fill="none" d="M0 0h24v24H0z" />
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" <path
/></svg d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z"
> /></svg
<span>{$_("donations")}</span> >
</a> <span>{$_("orgs")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("TRACK:GET")} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
class:activenav={$router.path === "/tracks/"} <a
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:activenav={$router.path.includes("/donors/")}
href="/tracks/" 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="/donors/"
<svg >
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" <svg
fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
width="24" fill="currentColor"
height="24" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
viewBox="0 0 640 512" width="24"
><path height="24"
fill="currentColor" ><path fill="none" d="M0 0h24v24H0z" />
d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" <path
/></svg d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"
> /></svg
<span>{$_("tracks")}</span> >
</a> <span>{$_("donors")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")}
class:activenav={$router.path === "/cards/"} <a
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:activenav={$router.path.includes("/donations/")}
href="/cards/" 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="/donations/"
<svg >
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" <svg
fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
width="24" fill="currentColor"
height="24" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
viewBox="0 0 24 24" width="24"
> height="24"
<path fill="none" d="M0 0h24v24H0z" /> ><path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg
/></svg >
> <span>{$_("donations")}</span>
<span>{$_("cards")}</span> </a>
</a> {/if}
{/if} {#if store.state.jwtinfo.userdetails.permissions.includes("TRACK:GET")}
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")} <a
<a class:activenav={$router.path === "/tracks/"}
class:activenav={$router.path.includes("/scans/")} 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="/tracks/"
href="/scans/" >
> <svg
<svg class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" fill="currentColor"
fill="currentColor" width="24"
width="24" height="24"
height="24" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"
viewBox="0 0 24 24" ><path
><path fill="none" d="M0 0h24v24H0z" /> fill="currentColor"
<path d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z"
fill="currentColor" /></svg
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" >
/></svg <span>{$_("tracks")}</span>
> </a>
<span>Scans</span> {/if}
</a> {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
{/if} <a
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:GET")} class:activenav={$router.path === "/cards/"}
<a 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:activenav={$router.path.includes("/contacts/")} href="/cards/"
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="/contacts/" <svg
> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
<svg fill="currentColor"
fill="currentColor" width="24"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" >
height="24" <path fill="none" d="M0 0h24v24H0z" />
><path fill="none" d="M0 0h24v24H0z" /> <path
<path fill="currentColor"
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg /></svg
> >
<span>{$_("contacts")}</span> <span>{$_("cards")}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")} {#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
<a <a
class:activenav={$router.path.includes("/scanstations/")} class:activenav={$router.path.includes("/scans/")}
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="/scanstations/" href="/scans/"
> >
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" /> ><path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" fill="currentColor"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z"
/></svg /></svg
> >
<span>{$_("scanstations")}</span> <span>Scans</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:GET")} {#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:GET")}
<a <a
class:activenav={$router.path.includes("/statsclients/")} class:activenav={$router.path.includes("/contacts/")}
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="/statsclients/" href="/contacts/"
> >
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" fill="currentColor"
fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
width="24" xmlns="http://www.w3.org/2000/svg"
height="24" viewBox="0 0 24 24"
viewBox="0 0 24 24" width="24"
xmlns="http://www.w3.org/2000/svg" height="24"
><path fill="none" d="M0 0h24v24H0z" /> ><path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg
/></svg >
> <span>{$_("contacts")}</span>
<span>{$_("statsclients")}</span> </a>
</a> {/if}
{/if} {#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")}
{#if store.state.jwtinfo.userdetails.permissions.includes("USER:GET")} <a
<a class:activenav={$router.path.includes("/scanstations/")}
class:activenav={$router.path.includes("/users/")} 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="/scanstations/"
href="/users/" >
> <svg
<svg class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
width="24" width="24"
height="24" height="24"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" viewBox="0 0 24 24"
fill="currentColor" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" ><path fill="none" d="M0 0h24v24H0z" />
><path fill="none" d="M0 0h24v24H0z" /> <path
<path fill="currentColor"
d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
/></svg /></svg
> >
<span>{$_("users")}</span> <span>{$_("scanstations")}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:GET")} {#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:GET")}
<a <a
class:activenav={$router.path.includes("/groups/")} class:activenav={$router.path.includes("/statsclients/")}
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="/groups/" href="/statsclients/"
> >
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
viewBox="0 0 640 512" xmlns="http://www.w3.org/2000/svg"
><path ><path fill="none" d="M0 0h24v24H0z" />
fill="currentColor" <path
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" fill="currentColor"
/></svg d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
> /></svg
<span>{$_("user-groups")}</span> >
</a> <span>{$_("statsclients")}</span>
{/if} </a>
<a {/if}
class:activenav={$router.path === "/settings/"} {#if store.state.jwtinfo.userdetails.permissions.includes("USER:GET")}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" <a
href="/settings/" class:activenav={$router.path.includes("/users/")}
> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
<svg href="/users/"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" >
xmlns="http://www.w3.org/2000/svg" <svg
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
fill="currentColor" width="24"
> height="24"
<path class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
fill-rule="evenodd" fill="currentColor"
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" viewBox="0 0 24 24"
clip-rule="evenodd" ><path fill="none" d="M0 0h24v24H0z" />
/> <path
</svg> d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z"
<span>{$_("settings")}</span> /></svg
</a> >
<a <span>{$_("users")}</span>
class:activenav={$router.path === "/about/"} </a>
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" {/if}
href="/about/" {#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:GET")}
> <a
<svg class:activenav={$router.path.includes("/groups/")}
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
xmlns="http://www.w3.org/2000/svg" href="/groups/"
fill="none" >
stroke="currentColor" <svg
stroke-width="2" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
stroke-linecap="round" fill="currentColor"
stroke-linejoin="round" width="24"
viewBox="0 0 24 24" height="24"
><circle cx="12" cy="12" r="10" /> xmlns="http://www.w3.org/2000/svg"
<path d="M12 16v-4M12 8h.01" /></svg viewBox="0 0 640 512"
> ><path
<span>{$_("about")}</span> fill="currentColor"
</a> d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"
<button /></svg
tabindex="0" >
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" <span>{$_("user-groups")}</span>
on:click={() => { </a>
AuthService.authControllerLogout(); {/if}
logout(); <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase">
}} {$_("system")}
> </h2>
<svg <a
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" class:activenav={$router.path === "/settings/"}
fill="currentColor" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
width="24" href="/settings/"
height="24" >
xmlns="http://www.w3.org/2000/svg" <svg
viewBox="0 0 24 24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
><path fill="none" d="M0 0h24v24H0z" /> xmlns="http://www.w3.org/2000/svg"
<path viewBox="0 0 20 20"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" fill="currentColor"
/></svg >
> <path
<span>{$_("logout")}</span> fill-rule="evenodd"
</button> d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
</nav> clip-rule="evenodd"
</div> />
<div class="ml-0 transition md:ml-60"> </svg>
<header <span>{$_("settings")}</span>
class="flex items-center w-full px-4 bg-white border-b h-14 md:hidden" </a>
> <a
<button class:activenav={$router.path === "/about/"}
on:click={() => { class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
navOpen = true; href="/about/"
}} >
class="block btn btn-light md:hidden" <svg
> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
<span class="sr-only">Menu</span><svg xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" fill="none"
fill="none" stroke="currentColor"
viewBox="0 0 24 24" stroke-width="2"
stroke-width="1.5" stroke-linecap="round"
stroke="currentColor" stroke-linejoin="round"
class="size-6" viewBox="0 0 24 24"
> ><circle cx="12" cy="12" r="10" />
<path <path d="M12 16v-4M12 8h.01" /></svg
stroke-linecap="round" >
stroke-linejoin="round" <span>{$_("about")}</span>
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" </a>
/> <button
</svg> tabindex="0"
</button> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
<span class="inline-block"> on:click={() => {
<img src="/lfk-logo.png" alt="Logo" class="h-8 inline-block" /> AuthService.authControllerLogout();
<span class="text-lg font-bold">LfK!Admin</span> logout();
</span> }}
</header> >
<Toaster position="top-right" /> <svg
<slot> class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
<NoComponentLoaded /> fill="currentColor"
</slot> width="24"
</div> height="24"
{#if navOpen === true} xmlns="http://www.w3.org/2000/svg"
<button viewBox="0 0 24 24"
on:click={() => { ><path fill="none" d="M0 0h24v24H0z" />
navOpen = false; <path
}} d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z"
class:hidden={!navOpen} /></svg
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" >
/> <span>{$_("logout")}</span>
{/if} </button>
</nav>
</div>
<div class="ml-0 transition md:ml-60">
<header
class="flex items-center w-full px-4 bg-white border-b h-14 md:hidden"
>
<button
on:click={() => {
navOpen = true;
}}
class="block btn btn-light md:hidden"
>
<span class="sr-only">Menu</span><svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
</button>
<span class="inline-block">
<img src="/lfk-logo.png" alt="Logo" class="h-8 inline-block" />
<span class="text-lg font-bold">LfK!Admin</span>
</span>
</header>
<Toaster position="top-right" />
<slot>
<NoComponentLoaded />
</slot>
</div>
{#if navOpen === true}
<button
on:click={() => {
navOpen = false;
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden"
/>
{/if}
</section> </section>
<style> <style>
.collapsed_navigation { .collapsed_navigation {
transform: translateX(-100%); transform: translateX(-100%);
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.collapsed_navigation { .collapsed_navigation {
transform: translateX(0px); transform: translateX(0px);
} }
} }
</style> </style>

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

@@ -1,197 +1,256 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { import {
RunnerCardService, RunnerCardService,
RunnerOrganizationService, RunnerOrganizationService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import toast from "svelte-french-toast"; import toast from "svelte-french-toast";
import DocumentServer from "./DocumentServer.ts"; import DocumentServer from "./DocumentServer.ts";
import { init } from "@paralleldrive/cuid2"; import { init } from "@paralleldrive/cuid2";
const createId = init({ length: 10, fingerprint: "lfk-frontend" }); const createId = init({ length: 10, fingerprint: "lfk-frontend" });
const documentServer = new DocumentServer( const documentServer = new DocumentServer(
config.baseurl_documentserver, config.baseurl_documentserver,
config.documentserver_key config.documentserver_key
); );
export let cards_show = false; export let cards_show = false;
export let generate_cards = []; export let generate_cards = [];
export let generate_runners = []; export let generate_runners = [];
export let generate_orgs = []; export let generate_orgs = [];
export let generate_teams = []; export let generate_teams = [];
function download(blob, fileName) { function download(blob, fileName) {
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
let a = document.createElement("a"); let a = document.createElement("a");
a.href = url; a.href = url;
a.download = fileName; a.download = fileName;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
a.remove(); a.remove();
toast.dismiss(); toast.dismiss();
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){
} else if (generate_teams.length > 0) { generateOrgCardsCombined(locale);
generateTeamCards(locale);
} else if (generate_runners.length > 0) {
generateRunnersCards(locale);
} else { } else {
generateCards(locale); generateOrgCards(locale)
} }
} } else if (generate_teams.length > 0) {
generateTeamCards(locale);
} else if (generate_runners.length > 0) {
generateRunnersCards(locale);
} else {
generateCards(locale);
}
}
function generateCards(locale) { function generateCards(locale) {
toast.loading($_("generating-pdf")); toast.loading($_("generating-pdf"));
documentServer documentServer
.generateCards(generate_cards, locale) .generateCards(generate_cards, locale)
.then((blob) => { .then((blob) => {
download(blob, `${$_("runnercards")}-${locale}-${createId()}.pdf`); download(blob, `${$_("runnercards")}-${locale}-${createId()}.pdf`);
}) })
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
}); });
} }
async function generateRunnersCards(locale) { async function generateRunnersCards(locale) {
toast.loading($_("generating-pdf")); toast.loading($_("generating-pdf"));
const current_cards = await RunnerCardService.runnerCardControllerGetAll(); const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let cards = []; let cards = [];
for (let runner of generate_runners) { for (let runner of generate_runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id); let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) { if (!card) {
card = await RunnerCardService.runnerCardControllerPost({ card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id, runner: runner.id,
}); });
} }
cards.push(card); cards.push(card);
} }
documentServer documentServer
.generateCards(cards, locale) .generateCards(cards, locale)
.then((blob) => { .then((blob) => {
let fileName = `${$_("runnercards")}-${locale}-${createId()}.pdf`; let fileName = `${$_("runnercards")}-${locale}-${createId()}.pdf`;
if (generate_runners.length == 1) { if (generate_runners.length == 1) {
fileName = `${$_("runnercards")}_${generate_runners[0].firstname}_${ fileName = `${$_("runnercards")}_${generate_runners[0].firstname}_${
generate_runners[0].lastname generate_runners[0].lastname
}-${locale}-${createId()}.pdf`; }-${locale}-${createId()}.pdf`;
} }
download(blob, fileName); download(blob, fileName);
}) })
.catch((err) => {}); .catch((err) => {});
} }
async function generateTeamCards(locale) { async function generateTeamCards(locale) {
toast.loading($_("generating-pdfs")); toast.loading($_("generating-pdfs"));
let count = 0; let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll(); const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for (const t of generate_teams) { for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id t.id
); );
let cards = []; let cards = [];
for (let runner of runners) { for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id); let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) { if (!card) {
card = await RunnerCardService.runnerCardControllerPost({ card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id, runner: runner.id,
}); });
} }
cards.push(card); cards.push(card);
} }
documentServer documentServer
.generateCards(cards, locale) .generateCards(cards, locale)
.then((blob) => { .then((blob) => {
download( download(
blob, blob,
`${$_("runnercards")}_${t.name}-${locale}-${createId()}.pdf` `${$_("runnercards")}_${t.name}-${locale}-${createId()}.pdf`
); );
}) })
.catch((err) => {}); .catch((err) => {});
} }
} }
async function generateOrgCards(locale) { async function generateOrgCards(locale) {
toast.loading($_("generating-pdfs")); toast.loading($_("generating-pdfs"));
const current_cards = await RunnerCardService.runnerCardControllerGetAll(); const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let count = 0; let count = 0;
let count_orgs = 0; let count_orgs = 0;
for (const o of generate_orgs) { for (const o of generate_orgs) {
count_orgs++; count_orgs++;
let count = 0; let count = 0;
let runners = let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners( await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id, o.id,
true true
); );
let cards = []; let cards = [];
for (let runner of runners) { for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id); let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) { if (!card) {
card = await RunnerCardService.runnerCardControllerPost({ card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id, runner: runner.id,
}); });
} }
cards.push(card); cards.push(card);
} }
await documentServer await documentServer
.generateCards(cards, locale) .generateCards(cards, locale)
.then((blob) => { .then((blob) => {
download( download(
blob, blob,
`${$_("runnercards")}_${o.name}_direct-${locale}-${createId()}.pdf` `${$_("runnercards")}_${o.name}_direct-${locale}-${createId()}.pdf`
); );
}) })
.catch((err) => {}); .catch((err) => {});
for (const t of o.teams) { for (const t of o.teams) {
count++; count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners( let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id t.id
); );
let cards = []; let cards = [];
for (let runner of runners) { for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id); let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) { if (!card) {
card = await RunnerCardService.runnerCardControllerPost({ card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id, runner: runner.id,
}); });
} }
cards.push(card); cards.push(card);
} }
await documentServer await documentServer
.generateCards(cards, locale) .generateCards(cards, locale)
.then((blob) => { .then((blob) => {
download( download(
blob, blob,
`${$_("runnercards")}_${o.name}_${ `${$_("runnercards")}_${o.name}_${
t.name t.name
}-${locale}-${createId()}.pdf` }-${locale}-${createId()}.pdf`
); );
}) })
.catch((err) => {}); .catch((err) => {});
} }
} }
} }
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}
<button <button
on:click={() => { on:click={() => {
generateRunnerCards("de"); generateRunnerCards("de");
}} }}
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" on:contextmenu|preventDefault={() => {
> generateRunnerCards("de", true);
{$_("generate-runnercards")}: DE }}
</button> 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"
<button >
on:click={() => { {$_("generate-runnercards")}: DE
generateRunnerCards("en"); </button>
}} <button
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" on:click={() => {
> generateRunnerCards("en");
{$_("generate-runnercards")}: EN }}
</button> 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"
>
{$_("generate-runnercards")}: EN
</button>
{/if} {/if}

View File

@@ -1,337 +1,342 @@
<script> <script>
import { import {
RunnerOrganizationService, RunnerOrganizationService,
RunnerService, RunnerService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import { import {
createSvelteTable, createSvelteTable,
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
renderComponent, renderComponent,
} from "@tanstack/svelte-table"; } from "@tanstack/svelte-table";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import InputElement from "../shared/InputElement.svelte"; import InputElement from "../shared/InputElement.svelte";
import TableActions from "../shared/TableActions.svelte"; import TableActions from "../shared/TableActions.svelte";
import { groupFilter } from "../shared/tablefilters"; import { groupFilter } from "../shared/tablefilters";
import DeleteRunnerModal from "./DeleteRunnerModal.svelte"; import DeleteRunnerModal from "./DeleteRunnerModal.svelte";
import TableBottom from "../shared/TableBottom.svelte"; import TableBottom from "../shared/TableBottom.svelte";
import TableHeader from "../shared/TableHeader.svelte"; import TableHeader from "../shared/TableHeader.svelte";
$: selectedRunners = $: selectedRunners =
$table?.getSelectedRowModel().rows.map((row) => row.original) || []; $table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected = $: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || []; $table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined; $: active_delete = undefined;
let dataLoaded = false; let dataLoaded = false;
export let created_via = "all"; export let created_via = "all";
export let current_runners = []; export let current_runners = [];
$: sponsoring_contracts_show = selected.length > 0; $: sponsoring_contracts_show = selected.length > 0;
$: cards_show = selected.length > 0; $: cards_show = selected.length > 0;
$: certificates_show = selected.length > 0; $: certificates_show = selected.length > 0;
$: teams = []; $: teams = [];
$: orgs = []; $: orgs = [];
export const addRunners = (runners) => { export const addRunners = (runners) => {
current_runners = current_runners.concat(...runners); current_runners = current_runners.concat(...runners);
options.update((options) => ({ options.update((options) => ({
...options, ...options,
data: current_runners, data: current_runners,
})); }));
}; };
//Section table //Section table
const columns = [ const columns = [
{ {
accessorKey: "id", accessorKey: "id",
header: () => "id", header: () => "id",
filterFn: `equalsString`, filterFn: `equalsString`,
}, },
{ {
accessorKey: "firstname", accessorKey: "firstname",
header: () => $_("first-name"), header: () => $_("first-name"),
filterFn: `includesString`, filterFn: `includesString`,
}, },
{ {
accessorKey: "middlename", accessorKey: "middlename",
header: () => $_("middle-name"), header: () => $_("middle-name"),
cell: (info) => { cell: (info) => {
if (!info || !info.getValue()) { if (!info || !info.getValue()) {
return ""; return "";
} }
return info.getValue(); return info.getValue();
}, },
filterFn: `includesString`, filterFn: `includesString`,
}, },
{ {
accessorKey: "lastname", accessorKey: "lastname",
header: () => $_("last-name"), header: () => $_("last-name"),
filterFn: `includesString`, filterFn: `includesString`,
}, },
{ {
accessorKey: "created_via", accessorKey: "created_via",
header: () => "created_via", header: () => "created_via",
filterFn: `includesString`, filterFn: `includesString`,
}, },
{ {
accessorKey: "group", accessorKey: "group",
header: () => $_("group"), header: () => $_("group"),
cell: (info) => { cell: (info) => {
const group = info.getValue(); const group = info.getValue();
if (group.responseType === "RUNNERORGANIZATION") { if (group.responseType === "RUNNERORGANIZATION") {
return group.name; return group.name;
} }
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(rowB.original.group.name); return rowA.original.group.name.localeCompare(rowB.original.group.name);
}, },
}, },
{ {
accessorKey: "distance", accessorKey: "distance",
header: () => $_("distance"), header: () => $_("distance"),
sortingFn: (rowA, rowB, col) => { sortingFn: (rowA, rowB, col) => {
return rowA.original.distance > rowB.original.distance; 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`;
} }
return `${(info.getValue() / 1000).toFixed(1)} km`; return `${(info.getValue() / 1000).toFixed(1)} km`;
}, },
enableColumnFilter: false, enableColumnFilter: false,
}, },
{ {
accessorKey: "actions", accessorKey: "actions",
header: () => $_("action"), header: () => $_("action"),
cell: (info) => { cell: (info) => {
return renderComponent(TableActions, { return renderComponent(TableActions, {
detailsLink: `/runners/${info.row.original.id}`, detailsLink: `/runners/${info.row.original.id}`,
deleteAction: () => { deleteAction: () => {
active_delete = active_delete =
current_runners[ current_runners[
current_runners.findIndex((r) => r.id == info.row.original.id) current_runners.findIndex((r) => r.id == info.row.original.id)
]; ];
}, },
deleteEnabled: deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes( store.state.jwtinfo.userdetails.permissions.includes(
"RUNNER:DELETE" "RUNNER:DELETE"
), ),
}); });
}, },
enableColumnFilter: false, enableColumnFilter: false,
enableSorting: false, enableSorting: false,
}, },
]; ];
const options = writable({ const options = writable({
data: [], data: [],
columns: columns, columns: columns,
filterFns: { filterFns: {
group: groupFilter, group: groupFilter,
}, },
initialState: { initialState: {
pagination: { pagination: {
pageSize: 50, pageSize: 50,
}, },
}, },
enableRowSelection: true, enableRowSelection: true,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
}); });
const table = createSvelteTable(options); const table = createSvelteTable(options);
async function deleteRunner(delete_runner_id) { async function deleteRunner(delete_runner_id) {
await RunnerService.runnerControllerRemove(delete_runner_id, true); await RunnerService.runnerControllerRemove(delete_runner_id, true);
current_runners = current_runners.filter((r) => r.id !== delete_runner_id); current_runners = current_runners.filter((r) => r.id !== delete_runner_id);
options.update((options) => ({ options.update((options) => ({
...options, ...options,
data: current_runners, data: current_runners,
})); }));
toast.success($_("runner-deleted")); toast.success($_("runner-deleted"));
} }
onMount(async () => { onMount(async () => {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val; teams = val;
}); });
RunnerOrganizationService.runnerOrganizationControllerGetAll().then( RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => { (val) => {
orgs = val; orgs = val;
} }
); );
let page = 0; let page = 0;
while (page >= 0) { while (page >= 0) {
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; }
}
current_runners = current_runners.concat(...runners); current_runners = current_runners.concat(...runners);
options.update((options) => ({ options.update((options) => ({
...options, ...options,
data: current_runners, data: current_runners,
})); }));
dataLoaded = true; dataLoaded = true;
page++; page++;
} }
}); });
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import AddRunnerModal from "./AddRunnerModal.svelte"; import AddRunnerModal from "./AddRunnerModal.svelte";
import ImportRunnerModal from "./ImportRunnerModal.svelte"; import ImportRunnerModal from "./ImportRunnerModal.svelte";
import toast from "svelte-french-toast"; import toast from "svelte-french-toast";
$: 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"} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<p>created_via={created_via}</p> <button
{/if} on:click={() => {
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} modal_open = true;
<button }}
on:click={() => { type="button"
modal_open = true; class="w-full inline-flex 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"
}} >
type="button" {$_("laeufer-hinzufuegen")}
class="w-full inline-flex 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" </button>
> <button
{$_("laeufer-hinzufuegen")} on:click={() => {
</button> import_modal_open = true;
<button }}
on:click={() => { type="button"
import_modal_open = true; class="w-full inline-flex 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"
}} >
type="button" {$_("import-runners")}
class="w-full inline-flex 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" </button>
> {/if}
{$_("import-runners")} <DeleteRunnerModal
</button> delete_runner={active_delete}
{/if} modal_open={active_delete != undefined}
<DeleteRunnerModal on:delete={(event) => {
delete_runner={active_delete} deleteRunner(event.detail.id);
modal_open={active_delete != undefined} }}
on:delete={(event) => { />
deleteRunner(event.detail.id); {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")}
}} {#if !dataLoaded}
/> <div
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")} class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
{#if !dataLoaded} role="alert"
<div >
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" <p class="font-bold">{$_("runners-are-being-loaded")}</p>
role="alert" <p class="text-sm">{$_("this-might-take-a-moment")}</p>
> </div>
<p class="font-bold">{$_("runners-are-being-loaded")}</p> {:else}
<p class="text-sm">{$_("this-might-take-a-moment")}</p> <GenerateSponsoringContracts
</div> bind:sponsoring_contracts_show
{:else} bind:generate_runners={selectedRunners}
<GenerateSponsoringContracts />
bind:sponsoring_contracts_show <GenerateRunnerCards
bind:generate_runners={selectedRunners} bind:cards_show
/> bind:generate_runners={selectedRunners}
<GenerateRunnerCards />
bind:cards_show <GenerateRunnerCertificates
bind:generate_runners={selectedRunners} bind:certificates_show
/> bind:generate_runners={selectedRunners}
<GenerateRunnerCertificates />
bind:certificates_show <div class="overflow-x-auto">
bind:generate_runners={selectedRunners} <table class="w-full">
/> <thead class="border-b border-gray-400">
<div class="overflow-x-auto"> {#each $table.getHeaderGroups() as headerGroup}
<table class="w-full"> <tr class="select-none">
<thead class="border-b border-gray-400"> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
{#each $table.getHeaderGroups() as headerGroup} <InputElement
<tr class="select-none"> type="checkbox"
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> checked={$table.getIsAllRowsSelected()}
<InputElement indeterminate={$table.getIsSomeRowsSelected()}
type="checkbox" on:change={() => $table.toggleAllRowsSelected()}
checked={$table.getIsAllRowsSelected()} />
indeterminate={$table.getIsSomeRowsSelected()} </th>
on:change={() => $table.toggleAllRowsSelected()} {#each headerGroup.headers as header}
/> <TableHeader {header} />
</th> {/each}
{#each headerGroup.headers as header} </tr>
<TableHeader {header} /> {/each}
{/each} </thead>
</tr> <tbody>
{/each} {#each $table.getRowModel().rows as row}
</thead> <tr class="odd:bg-white even:bg-gray-100">
<tbody> <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
{#each $table.getRowModel().rows as row} <InputElement
<tr class="odd:bg-white even:bg-gray-100"> type="checkbox"
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> checked={row.getIsSelected()}
<InputElement on:change={() => row.toggleSelected()}
type="checkbox" />
checked={row.getIsSelected()} </td>
on:change={() => row.toggleSelected()} {#each row.getVisibleCells() as cell}
/> <td>
</td> <svelte:component
{#each row.getVisibleCells() as cell} this={flexRender(
<td> cell.column.columnDef.cell,
<svelte:component cell.getContext()
this={flexRender( )}
cell.column.columnDef.cell, />
cell.getContext() </td>
)} {/each}
/> </tr>
</td> {/each}
{/each} </tbody>
</tr> </table>
{/each} </div>
</tbody> <div class="h-2" />
</table> {/if}
</div> {/if}
<div class="h-2" /> <TableBottom {table} {selected} />
{/if}
{/if}
<TableBottom {table} {selected} />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<AddRunnerModal <AddRunnerModal
bind:modal_open bind:modal_open
on:created={(event) => { on:created={(event) => {
addRunners(event.detail.runners); addRunners(event.detail.runners);
}} }}
/> />
<ImportRunnerModal <ImportRunnerModal
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
import_modal_open = false; import_modal_open = false;
}} }}
passed_team={{}} passed_team={{}}
passed_orgs={[]} passed_orgs={[]}
passed_org={{}} passed_org={{}}
opened_from="RunnerOverview" opened_from="RunnerOverview"
bind:import_modal_open bind:import_modal_open
on:created={(event) => { on:created={(event) => {
addRunners(event.detail.runners); addRunners(event.detail.runners);
}} }}
/> />
{/if} {/if}
<style> <style>
table tbody tr td:nth-child(2) { table tbody tr td:nth-child(2) {
font-family: monospace; font-family: monospace;
} }
</style> </style>

View File

@@ -0,0 +1,296 @@
<script>
import { _ } from "svelte-i18n";
import {
DonationService,
DonorService,
RunnerService,
} from "@odit/lfk-client-js";
import Svelecte from "svelecte";
import Select from "svelte-select";
import toast from "svelte-french-toast";
let runners = [];
let donors = [];
let runnerinfo = { id: 0, firstname: "", lastname: "" };
let donorinfo = { id: 0, firstname: "", lastname: "" };
let address = {
address1: "",
address2: "",
city: "",
postalcode: "",
country: "Germany",
};
let amount = 0;
let lastname = "";
let address_checked = false;
RunnerService.runnerControllerGetAll()
.then((val) => {
runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
})
.catch((err) => {
console.log("error fetching runners:", err);
});
function loadDonors() {
DonorService.donorControllerGetAll()
.then((val) => {
donors = val.map((r) => {
return { label: getDonorlabel(r), value: r };
});
console.log("refreshed donors");
setTimeout(() => {
loadDonors;
}, 30000);
})
.catch((err) => {
console.log("error fetching donors:", err);
});
}
loadDonors();
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const getDonorlabel = (option) => `${option.firstname} (${option.lastname})`;
const filterRunners = (label, filterText, option) => {
if (filterText.startsWith("#")) {
return option.value.id == parseInt(filterText.replace("#", ""));
}
return (
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase())
);
};
function resetAll() {
runnerinfo = { id: 0, firstname: "", lastname: "" };
donorinfo = { id: 0, firstname: "", lastname: "" };
amount = 0;
}
</script>
<div class="p-4">
<h3 class="text-3xl font-bold">{$_("fast_donation_create")}</h3>
<!-- -->
<div class="grid grid-cols-6 gap-4">
<div class="col-span-2">
<h4 class="text-xl font-semibold">
{$_("runner")}
</h4>
<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"
itemFilter={(label, filterText, option) =>
filterRunners(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
on:select={(selectedValue) => {
runnerinfo = selectedValue.detail.value;
}}
on:clear={() => (runnerinfo.runner = null)}
/>
</div>
<div class="col-span-2">
<h4 class="text-xl font-semibold">
{$_("donor")}
</h4>
<div class="mb-2">
<Svelecte
name="donor_fistname"
placeholder={$_("first-name")}
clearable={true}
options={donors}
keepCreated={false}
creatable={true}
labelField="label"
on:change={(e) => {
if (!e.detail?.value) {
donorinfo = { id: 0, firstname: "", lastname: "" };
return;
}
if (!e.detail?.$created) {
donorinfo = e.detail.value;
lastname = e.detail.value.lastname;
} else {
console.log("created option", e);
donorinfo.firstname = e.detail.value;
}
}}
class="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-0.5"
/>
<input
autocomplete="off"
placeholder={$_("last-name")}
class:border-red-500={donorinfo.lastname?.length == 0}
class:focus:border-red-500={donorinfo.lastname?.length == 0}
class:focus:ring-red-500={donorinfo.lastname?.length == 0}
bind:value={lastname}
on:input={e => {
donorinfo.lastname = e.target.value;
}}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
{#if donorinfo.id == 0}
<input
bind:checked={address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 size-4 text-indigo-600 border-gray-300 rounded"
/>
{:else}
<input
checked={true}
disabled
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 size-4 text-indigo-600 border-gray-300 rounded"
/>
{/if}
</div>
<div class="ml-3 text-sm">
<label for="comments" class="font-semibold text-gray-700"
>{$_("receipt-needed")}</label
>
</div>
</div>
{#if address_checked}
<div class="col-span-6">
<label for="address1" class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={address.address1.length == 0}
class:focus:border-red-500={address.address1.length == 0}
class:focus:ring-red-500={address.address1.length == 0}
bind:value={address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label for="address2" class="block text-sm font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
<input
autocomplete="off"
placeholder={$_("apartment-suite-etc")}
bind:value={address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label for="zipcode" class="block text-sm font-medium text-gray-700"
>{$_("zip-postal-code")}</label
>
<input
autocomplete="off"
placeholder={$_("zip-postal-code")}
class:border-red-500={address.postalcode.length == 0}
class:focus:border-red-500={address.postalcode.length == 0}
class:focus:ring-red-500={address.postalcode.length == 0}
bind:value={address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
<div class="col-span-6">
<label for="city" class="block text-sm font-medium text-gray-700"
>City</label
>
<input
autocomplete="off"
placeholder="City"
class:border-red-500={address.city.length == 0}
class:focus:border-red-500={address.city.length == 0}
class:focus:ring-red-500={address.city.length == 0}
bind:value={address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-neutral-800 rounded-md p-2"
/>
</div>
{/if}
</div>
<div>
<h4 class="text-xl font-semibold">
{$_("amount-per-kilometer")}
</h4>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!amount > 0}
class:focus:border-red-500={!amount > 0}
class:focus:ring-red-500={!amount > 0}
bind:value={amount}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-neutral-300 border bg-neutral-50 text-neutral-800 p-2"
placeholder="2.00"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-neutral-300 bg-neutral-50 text-neutral-500 text-sm"
></span
>
</div>
</div>
<div>
<h4 class="text-xl font-semibold">
{$_("confirm")}
</h4>
<button
disabled={amount <= 0 ||
runnerinfo.id == 0 ||
(donorinfo.firstname.length == 0 || donorinfo.lastname.length == 0)}
class="py-2 px-4 text-center inline-flex items-center text-md 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"
on:click={async () => {
toast.loading($_("creating-donation"));
if (donorinfo.id == 0) {
if (!address_checked) {
address = null
}
donorinfo = await DonorService.donorControllerPost({
firstname: donorinfo.firstname,
lastname: lastname,
receiptNeeded: address_checked,
address: address,
});
loadDonors();
}
await DonationService.donationControllerPostDistance({
amountPerDistance: amount*100,
runner: runnerinfo.id,
donor: donorinfo.id,
});
toast.dismiss();
toast.success($_("donation-created"));
resetAll();
}}>{$_("create")}</button
>
</div>
</div>
</div>
<style>
:global(:root) {
--sv-bg: #ffffff;
}
</style>

View File

@@ -0,0 +1,239 @@
<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) {
ScanService.scanControllerPostTrackScans({
card: parseInt(input),
station: selectedStation,
})
.then((data) => {
scaninfo = data;
if (scaninfo.valid) {
new Audio("/beep.mp3").play();
state = "scan_card";
} 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
);
}
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

@@ -134,6 +134,7 @@
"created-blanco-cards": "Blankokarten wurden erstellt", "created-blanco-cards": "Blankokarten wurden erstellt",
"created_via": "Erstellt von", "created_via": "Erstellt von",
"creating-blanco-cards": "Erstelle Blankokarten", "creating-blanco-cards": "Erstelle Blankokarten",
"creating-donation": "Sponsoring wird erstellt...",
"credits": "Credits", "credits": "Credits",
"csv_import__class": "Klasse", "csv_import__class": "Klasse",
"csv_import__firstname": "Vorname", "csv_import__firstname": "Vorname",
@@ -197,13 +198,15 @@
"documentation": "Dokumentation", "documentation": "Dokumentation",
"donation-amount": "Sponsoringbetrag", "donation-amount": "Sponsoringbetrag",
"donation-amount-must-be-greater-that-0-00eur": "Der Sponsoringbetrag muss größer als 0.00€ sein.", "donation-amount-must-be-greater-that-0-00eur": "Der Sponsoringbetrag muss größer als 0.00€ sein.",
"donation-created": "Sponsoring erstellt",
"donation-deleted": "Sponsoring gelöscht", "donation-deleted": "Sponsoring gelöscht",
"donation-quick-add": "Sponsoringschnelleingabe",
"donation-updated": "Sponsoring wurde aktualisiert", "donation-updated": "Sponsoring wurde aktualisiert",
"donation_added": "Sponsoring hinzugefügt", "donation_added": "Sponsoring hinzugefügt",
"donations": "Sponsorings", "donations": "Sponsorings",
"donations-are-being-loaded": "Sponsorings werden geladen...", "donations-are-being-loaded": "Sponsorings werden geladen...",
"done": "✅ Fertig", "done": "✅ Fertig",
"donor": "Sponsor", "donor": "Sponsor:in",
"donor-added": "Sponsor hinzugefügt", "donor-added": "Sponsor hinzugefügt",
"donor-deleted": "Sponsor gelöscht", "donor-deleted": "Sponsor gelöscht",
"donor-has-no-associated-donations": "Keine Sponsorings", "donor-has-no-associated-donations": "Keine Sponsorings",
@@ -229,6 +232,7 @@
"everything-concerning-your-profile": "Alles zu deinem Profil", "everything-concerning-your-profile": "Alles zu deinem Profil",
"faq": "FAQ", "faq": "FAQ",
"fast_card_replacement": "Karten-Schnellzusweisung (Mit Mobilgeräteunterstützung)", "fast_card_replacement": "Karten-Schnellzusweisung (Mit Mobilgeräteunterstützung)",
"fast_donation_create": "Sponsoring-Schnellanlage",
"festbetrag": "Festbetrag", "festbetrag": "Festbetrag",
"filename_sponsoringquittungsliste": "SponsoringQuittungsListe", "filename_sponsoringquittungsliste": "SponsoringQuittungsListe",
"filter-by-organization-team": "Filtern nach Organisation / Team", "filter-by-organization-team": "Filtern nach Organisation / Team",
@@ -296,9 +300,11 @@
"logout": "Abmelden", "logout": "Abmelden",
"mail-validation-in-progress": "E-Mail Verifizierung läuft... ", "mail-validation-in-progress": "E-Mail Verifizierung läuft... ",
"manage-admin-users": "Nutzer verwalten", "manage-admin-users": "Nutzer verwalten",
"management": "Verwaltung",
"middle-name": "Mittelname", "middle-name": "Mittelname",
"minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)", "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", "minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein",
"mobile-scanclient": "Mobiler Scanclient",
"must-be-at-least-10-characters-long": "Passwort muss mindestens 10 Zeichen lang sein!", "must-be-at-least-10-characters-long": "Passwort muss mindestens 10 Zeichen lang sein!",
"must-contain-a-lowercase-letter": "Passwort muss einen Großbuchstaben enthalten!", "must-contain-a-lowercase-letter": "Passwort muss einen Großbuchstaben enthalten!",
"must-contain-a-number": "Passwort muss eine Zahl enthalten!", "must-contain-a-number": "Passwort muss eine Zahl enthalten!",
@@ -375,13 +381,14 @@
"profile-deleted": "Profil gelöscht!", "profile-deleted": "Profil gelöscht!",
"profile-picture": "Profilbild", "profile-picture": "Profilbild",
"profile-updated": "Profil wurde aktualisiert!", "profile-updated": "Profil wurde aktualisiert!",
"quick-tools": "Werkzeuge",
"read-license": "Lizenz-Text lesen", "read-license": "Lizenz-Text lesen",
"receipt-needed": "Spendenquittung benötigt", "receipt-needed": "Spendenquittung benötigt",
"repo_link": "Link", "repo_link": "Link",
"request-a-new-reset-mail": "Neue Reset-Mail anfordern", "request-a-new-reset-mail": "Neue Reset-Mail anfordern",
"reset-my-password": "Passwort zurücksetzen", "reset-my-password": "Passwort zurücksetzen",
"reset-password": "Passwort zurücksetzen", "reset-password": "Passwort zurücksetzen",
"runner": "Läufer", "runner": "Läufer:in",
"runner-added": "Läufer hinzugefügt", "runner-added": "Läufer hinzugefügt",
"runner-deleted": "Läufer gelöscht", "runner-deleted": "Läufer gelöscht",
"runner-import": "Läufer Import", "runner-import": "Läufer Import",
@@ -403,6 +410,7 @@
"scan-with-fixed-distance": "Scan mit Festdistanz", "scan-with-fixed-distance": "Scan mit Festdistanz",
"scan_card": "Läuferkarte scannen", "scan_card": "Läuferkarte scannen",
"scan_runner": "Läufer scannen", "scan_runner": "Läufer scannen",
"scanclient": "Scanclient",
"scans": "Scans", "scans": "Scans",
"scans-are-being-loaded": "Scans werden geladen", "scans-are-being-loaded": "Scans werden geladen",
"scanstation": "Scanner Station", "scanstation": "Scanner Station",
@@ -439,6 +447,7 @@
"status": "Status", "status": "Status",
"stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können", "stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können",
"successful-password-reset": "Passwort erfolgreich zurückgesetzt!", "successful-password-reset": "Passwort erfolgreich zurückgesetzt!",
"system": "System",
"team": "Team", "team": "Team",
"team-added": "Team wurde erstellt", "team-added": "Team wurde erstellt",
"team-deleted": "Team gelöscht", "team-deleted": "Team gelöscht",

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",
@@ -134,6 +134,7 @@
"created-blanco-cards": "Created blanco cards", "created-blanco-cards": "Created blanco cards",
"created_via": "Erstellt über", "created_via": "Erstellt über",
"creating-blanco-cards": "Creating blanco cards", "creating-blanco-cards": "Creating blanco cards",
"creating-donation": "Creating donation...",
"credits": "Credits", "credits": "Credits",
"csv_import__class": "Class", "csv_import__class": "Class",
"csv_import__firstname": "Firstname", "csv_import__firstname": "Firstname",
@@ -197,7 +198,9 @@
"documentation": "Documentation", "documentation": "Documentation",
"donation-amount": "Donation amount", "donation-amount": "Donation amount",
"donation-amount-must-be-greater-that-0-00eur": "Donation amount must be greater that 0.00€", "donation-amount-must-be-greater-that-0-00eur": "Donation amount must be greater that 0.00€",
"donation-created": "Created sponsoring",
"donation-deleted": "Donation deleted", "donation-deleted": "Donation deleted",
"donation-quick-add": "Mass sponsoring creation",
"donation-updated": "Donation updated", "donation-updated": "Donation updated",
"donation_added": "Donation_added", "donation_added": "Donation_added",
"donations": "Donations", "donations": "Donations",
@@ -229,6 +232,7 @@
"everything-concerning-your-profile": "Everything concerning your profile", "everything-concerning-your-profile": "Everything concerning your profile",
"faq": "FAQ", "faq": "FAQ",
"fast_card_replacement": "Fast card replacement (with mobile support)", "fast_card_replacement": "Fast card replacement (with mobile support)",
"fast_donation_create": "Mass donation creator",
"festbetrag": "Fixed amount", "festbetrag": "Fixed amount",
"filename_sponsoringquittungsliste": "DonorReceiptList", "filename_sponsoringquittungsliste": "DonorReceiptList",
"filter-by-organization-team": "Filter by Organization/ Team", "filter-by-organization-team": "Filter by Organization/ Team",
@@ -299,6 +303,7 @@
"middle-name": "Middle name", "middle-name": "Middle name",
"minimum-lap-time-in-s": "minimum lap time in s", "minimum-lap-time-in-s": "minimum lap time in s",
"minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0", "minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0",
"mobile-scanclient": "Mobile scanclient",
"must-be-at-least-10-characters-long": "Must be at least 10 characters long!", "must-be-at-least-10-characters-long": "Must be at least 10 characters long!",
"must-contain-a-lowercase-letter": "Must contain a lowercase letter!", "must-contain-a-lowercase-letter": "Must contain a lowercase letter!",
"must-contain-a-number": "Must contain a number!", "must-contain-a-number": "Must contain a number!",
@@ -375,6 +380,7 @@
"profile-deleted": "Profile deleted!", "profile-deleted": "Profile deleted!",
"profile-picture": "Profile Picture", "profile-picture": "Profile Picture",
"profile-updated": "Profile updated!", "profile-updated": "Profile updated!",
"quick-tools": "Tools",
"read-license": "Read License", "read-license": "Read License",
"receipt-needed": "Receipt needed", "receipt-needed": "Receipt needed",
"repo_link": "Link", "repo_link": "Link",
@@ -388,7 +394,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",
@@ -403,6 +409,7 @@
"scan-with-fixed-distance": "Scan with fixed distance", "scan-with-fixed-distance": "Scan with fixed distance",
"scan_card": "Scan Card", "scan_card": "Scan Card",
"scan_runner": "Scan Runner", "scan_runner": "Scan Runner",
"scanclient": "Scanclient",
"scans": "Scans", "scans": "Scans",
"scans-are-being-loaded": "Scans are being loaded", "scans-are-being-loaded": "Scans are being loaded",
"scanstation": "Scanstation", "scanstation": "Scanstation",
@@ -439,6 +446,7 @@
"status": "Status", "status": "Status",
"stuff-that-could-harm-your-profile": "Stuff that could harm your profile", "stuff-that-could-harm-your-profile": "Stuff that could harm your profile",
"successful-password-reset": "Successful password reset!", "successful-password-reset": "Successful password reset!",
"system": "System",
"team": "Team", "team": "Team",
"team-added": "Team added", "team-added": "Team added",
"team-deleted": "Team deleted", "team-deleted": "Team deleted",
@@ -469,11 +477,11 @@
"token": "Token", "token": "Token",
"total-distance": "total distance", "total-distance": "total distance",
"total-donation-amount": "Total donations", "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",

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;
}); });

View File

@@ -2,7 +2,8 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte"; import { svelte } from "@sveltejs/vite-plugin-svelte";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import mkcert from 'vite-plugin-mkcert'
export default defineConfig({ export default defineConfig({
plugins: [svelte(), tailwindcss()], plugins: [svelte(), tailwindcss(), mkcert()],
}); });