Compare commits

...

10 Commits

16 changed files with 2340 additions and 1869 deletions

View File

@@ -2,8 +2,19 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.12.8](https://git.odit.services/lfk/frontend/compare/1.12.7...1.12.8)
- feat(dasboard): Added section headers to main nav [`3a8533a`](https://git.odit.services/lfk/frontend/commit/3a8533a7baef02f7bc9780ce37be1a350bd92270)
- fic(locales): Updated dashboard translations [`5ac6fe3`](https://git.odit.services/lfk/frontend/commit/5ac6fe30b5b9e34043c734d51d5da137fdf7ac38)
- feat(runners): Created_via filters can now be set via query params [`14501d3`](https://git.odit.services/lfk/frontend/commit/14501d3828dd0d48ba0baeeddf936ba275f7b9b7)
- refactor(tools): Move tools to tools route [`16dc789`](https://git.odit.services/lfk/frontend/commit/16dc789db5d9ea41774c77622a579cc0d9bd95f2)
- refactor(tools): Move tools into shared directory instead of the non-descript "general" [`e4f9b1a`](https://git.odit.services/lfk/frontend/commit/e4f9b1a60551d7955def4d068d534cf17b1ea640)
#### [1.12.7](https://git.odit.services/lfk/frontend/compare/1.12.6...1.12.7) #### [1.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) - 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) - refactor(store): update refresh interval from 2min to 60min [`00d1988`](https://git.odit.services/lfk/frontend/commit/00d198895e15174b70a8d229974b4baa7d0ed8fc)

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.7-RELEASE_INFO</span >RELEASE_INFO-1.12.8-RELEASE_INFO</span
> >
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script> <script src="/env.js"></script>

View File

@@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "1.12.7", "version": "1.12.8",
"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": {

174
pnpm-lock.yaml generated
View File

@@ -93,6 +93,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:
@@ -780,6 +783,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 +794,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 +831,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 +908,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 +991,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 +1006,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 +1027,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 +1137,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 +1173,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 +1221,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 +1240,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 +1563,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'}
@@ -2018,6 +2096,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}
@@ -2627,6 +2711,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 +2729,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 +2780,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 +2845,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 +2916,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 +2926,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 +2947,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 +3127,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 +3151,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 +3220,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 +3237,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 +3506,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
@@ -3880,6 +4045,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,8 @@
import Cards from "./components/cards/Cards.svelte"; import Cards from "./components/cards/Cards.svelte";
import StatsClients from "./components/statsclients/StatsClients.svelte"; import StatsClients from "./components/statsclients/StatsClients.svelte";
import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte"; import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte";
import CardReplacement from "./components/general/CardReplacement.svelte"; import CardReplacement from "./components/tools/CardReplacement.svelte";
import ScanClient from "./components/tools/ScanClient.svelte";
store.init(); store.init();
</script> </script>
@@ -126,21 +127,22 @@
<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>
</Route> </Route>
<Route path="/teams/*"> <Route path="/teams/*">

View File

@@ -1,479 +1,508 @@
<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")} <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase">
<a {$_("management")}
class:activenav={$router.path.includes("/teams/")} </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" <a
href="/teams/" class:activenav={$router.path.includes("/runners/")}
> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold"
<svg href="/runners/"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" >
fill="currentColor" <svg
width="24" xmlns="http://www.w3.org/2000/svg"
height="24" viewBox="0 0 24 24"
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 640 512" fill="currentColor"
><path width="24"
fill="currentColor" height="24"
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" ><path fill="none" d="M0 0h24v24H0z" />
/></svg <path
> 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"
<span>{$_("teams")}</span> /></svg
</a> >
{/if} <span>{$_("runners")}</span>
{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:GET")} </a>
<a {/if}
class:activenav={$router.path.includes("/orgs/")} {#if store.state.jwtinfo.userdetails.permissions.includes("TEAM: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="/orgs/" 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"
<svg href="/teams/"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" >
fill="currentColor" <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"
width="24" width="24"
height="24" height="24"
><path fill="none" d="M0 0h24v24H0z" /> xmlns="http://www.w3.org/2000/svg"
<path viewBox="0 0 640 512"
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 fill="currentColor"
> 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"
<span>{$_("orgs")}</span> /></svg
</a> >
{/if} <span>{$_("teams")}</span>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")} </a>
<a {/if}
class:activenav={$router.path.includes("/donors/")} {#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION: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="/donors/" class:activenav={$router.path.includes("/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"
<svg href="/orgs/"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" >
fill="currentColor" <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"
width="24" xmlns="http://www.w3.org/2000/svg"
height="24" viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" /> width="24"
<path height="24"
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" ><path fill="none" d="M0 0h24v24H0z" />
/></svg <path
> 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"
<span>{$_("donors")}</span> /></svg
</a> >
{/if} <span>{$_("orgs")}</span>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")} </a>
<a {/if}
class:activenav={$router.path.includes("/donations/")} {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR: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="/donations/" class:activenav={$router.path.includes("/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"
<svg href="/donors/"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" >
fill="currentColor" <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"
width="24" xmlns="http://www.w3.org/2000/svg"
height="24" viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" /> width="24"
<path height="24"
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 fill="none" d="M0 0h24v24H0z" />
/></svg <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"
<span>{$_("donations")}</span> /></svg
</a> >
{/if} <span>{$_("donors")}</span>
{#if store.state.jwtinfo.userdetails.permissions.includes("TRACK:GET")} </a>
<a {/if}
class:activenav={$router.path === "/tracks/"} {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION: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="/tracks/" class:activenav={$router.path.includes("/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"
<svg href="/donations/"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" >
fill="currentColor" <svg
width="24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
height="24" fill="currentColor"
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 width="24"
fill="currentColor" height="24"
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 fill="none" d="M0 0h24v24H0z" />
/></svg <path
> 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"
<span>{$_("tracks")}</span> /></svg
</a> >
{/if} <span>{$_("donations")}</span>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} </a>
<a {/if}
class:activenav={$router.path === "/cards/"} {#if store.state.jwtinfo.userdetails.permissions.includes("TRACK: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="/cards/" class:activenav={$router.path === "/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"
<svg href="/tracks/"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" >
fill="currentColor" <svg
width="24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
height="24" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" width="24"
viewBox="0 0 24 24" height="24"
> xmlns="http://www.w3.org/2000/svg"
<path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 640 512"
<path ><path
fill="currentColor" fill="currentColor"
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" 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"
/></svg /></svg
> >
<span>{$_("cards")}</span> <span>{$_("tracks")}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")} {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
<a <a
class:activenav={$router.path.includes("/scans/")} class:activenav={$router.path === "/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" 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="/scans/" href="/cards/"
> >
<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 24 24" viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" /> >
<path <path fill="none" d="M0 0h24v24H0z" />
fill="currentColor" <path
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" fill="currentColor"
/></svg 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
<span>Scans</span> >
</a> <span>{$_("cards")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:GET")} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
class:activenav={$router.path.includes("/contacts/")} <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("/scans/")}
href="/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"
> href="/scans/"
<svg >
fill="currentColor" <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" 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 24 24"
<path ><path fill="none" d="M0 0h24v24H0z" />
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" <path
/></svg fill="currentColor"
> d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z"
<span>{$_("contacts")}</span> /></svg
</a> >
{/if} <span>Scans</span>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")} </a>
<a {/if}
class:activenav={$router.path.includes("/scanstations/")} {#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT: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="/scanstations/" 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"
<svg href="/contacts/"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" >
fill="currentColor" <svg
width="24" fill="currentColor"
height="24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
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" /> width="24"
<path height="24"
fill="currentColor" ><path fill="none" d="M0 0h24v24H0z" />
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" <path
/></svg 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"
> /></svg
<span>{$_("scanstations")}</span> >
</a> <span>{$_("contacts")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:GET")} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")}
class:activenav={$router.path.includes("/statsclients/")} <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("/scanstations/")}
href="/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"
> href="/scanstations/"
<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" width="24"
viewBox="0 0 24 24" height="24"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" /> xmlns="http://www.w3.org/2000/svg"
<path ><path fill="none" d="M0 0h24v24H0z" />
fill="currentColor" <path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" fill="currentColor"
/></svg d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
> /></svg
<span>{$_("statsclients")}</span> >
</a> <span>{$_("scanstations")}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes("USER:GET")} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:GET")}
class:activenav={$router.path.includes("/users/")} <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("/statsclients/")}
href="/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"
> href="/statsclients/"
<svg >
xmlns="http://www.w3.org/2000/svg" <svg
width="24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
height="24" fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" width="24"
fill="currentColor" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" /> xmlns="http://www.w3.org/2000/svg"
<path ><path fill="none" d="M0 0h24v24H0z" />
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" <path
/></svg fill="currentColor"
> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
<span>{$_("users")}</span> /></svg
</a> >
{/if} <span>{$_("statsclients")}</span>
{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:GET")} </a>
<a {/if}
class:activenav={$router.path.includes("/groups/")} {#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="/groups/" 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" >
fill="currentColor" <svg
width="24" xmlns="http://www.w3.org/2000/svg"
height="24" width="24"
xmlns="http://www.w3.org/2000/svg" height="24"
viewBox="0 0 640 512" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
><path fill="currentColor"
fill="currentColor" viewBox="0 0 24 24"
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" ><path fill="none" d="M0 0h24v24H0z" />
/></svg <path
> 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>{$_("user-groups")}</span> /></svg
</a> >
{/if} <span>{$_("users")}</span>
<a </a>
class:activenav={$router.path === "/settings/"} {/if}
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("USERGROUP:GET")}
href="/settings/" <a
> class:activenav={$router.path.includes("/groups/")}
<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"
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" href="/groups/"
xmlns="http://www.w3.org/2000/svg" >
viewBox="0 0 20 20" <svg
fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
> fill="currentColor"
<path width="24"
fill-rule="evenodd" height="24"
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" xmlns="http://www.w3.org/2000/svg"
clip-rule="evenodd" viewBox="0 0 640 512"
/> ><path
</svg> fill="currentColor"
<span>{$_("settings")}</span> 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"
</a> /></svg
<a >
class:activenav={$router.path === "/about/"} <span>{$_("user-groups")}</span>
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="/about/" {/if}
> <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase">
<svg {$_("system")}
class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" </h2>
xmlns="http://www.w3.org/2000/svg" <a
fill="none" class:activenav={$router.path === "/settings/"}
stroke="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"
stroke-width="2" href="/settings/"
stroke-linecap="round" >
stroke-linejoin="round" <svg
viewBox="0 0 24 24" class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600"
><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 20 20"
> fill="currentColor"
<span>{$_("about")}</span> >
</a> <path
<button fill-rule="evenodd"
tabindex="0" 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"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" clip-rule="evenodd"
on:click={() => { />
AuthService.authControllerLogout(); </svg>
logout(); <span>{$_("settings")}</span>
}} </a>
> <a
<svg class:activenav={$router.path === "/about/"}
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"
fill="currentColor" href="/about/"
width="24" >
height="24" <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" xmlns="http://www.w3.org/2000/svg"
><path fill="none" d="M0 0h24v24H0z" /> fill="none"
<path stroke="currentColor"
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" stroke-width="2"
/></svg stroke-linecap="round"
> stroke-linejoin="round"
<span>{$_("logout")}</span> viewBox="0 0 24 24"
</button> ><circle cx="12" cy="12" r="10" />
</nav> <path d="M12 16v-4M12 8h.01" /></svg
</div> >
<div class="ml-0 transition md:ml-60"> <span>{$_("about")}</span>
<header </a>
class="flex items-center w-full px-4 bg-white border-b h-14 md:hidden" <button
> 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"
on:click={() => { on:click={() => {
navOpen = true; AuthService.authControllerLogout();
}} logout();
class="block btn btn-light md:hidden" }}
> >
<span class="sr-only">Menu</span><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"
fill="none" fill="currentColor"
viewBox="0 0 24 24" width="24"
stroke-width="1.5" height="24"
stroke="currentColor" xmlns="http://www.w3.org/2000/svg"
class="size-6" viewBox="0 0 24 24"
> ><path fill="none" d="M0 0h24v24H0z" />
<path <path
stroke-linecap="round" 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"
stroke-linejoin="round" /></svg
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" >
/> <span>{$_("logout")}</span>
</svg> </button>
</button> </nav>
<span class="inline-block"> </div>
<img src="/lfk-logo.png" alt="Logo" class="h-8 inline-block" /> <div class="ml-0 transition md:ml-60">
<span class="text-lg font-bold">LfK!Admin</span> <header
</span> class="flex items-center w-full px-4 bg-white border-b h-14 md:hidden"
</header> >
<Toaster position="top-right" /> <button
<slot> on:click={() => {
<NoComponentLoaded /> navOpen = true;
</slot> }}
</div> class="block btn btn-light md:hidden"
{#if navOpen === true} >
<button <span class="sr-only">Menu</span><svg
on:click={() => { xmlns="http://www.w3.org/2000/svg"
navOpen = false; fill="none"
}} viewBox="0 0 24 24"
class:hidden={!navOpen} stroke-width="1.5"
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" stroke="currentColor"
/> class="size-6"
{/if} >
<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,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,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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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()],
}); });