Compare commits
	
		
			11 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 51d9b35dc4 | |||
| 16dc789db5 | |||
| e4f9b1a605 | |||
| 3a8533a7ba | |||
| 5ac6fe30b5 | |||
| 14501d3828 | |||
| c78bdfa5e2 | |||
| b2ed2afd8a | |||
| 00d198895e | |||
| b5c079da9a | |||
| 93422b9779 | 
							
								
								
									
										26
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -2,8 +2,34 @@ | ||||
|  | ||||
| All notable changes to this project will be documented in this file. Dates are displayed in UTC. | ||||
|  | ||||
| #### [1.12.8](https://git.odit.services/lfk/frontend/compare/1.12.7...1.12.8) | ||||
|  | ||||
| - feat(dasboard): Added section headers to main nav [`3a8533a`](https://git.odit.services/lfk/frontend/commit/3a8533a7baef02f7bc9780ce37be1a350bd92270) | ||||
| - fic(locales): Updated dashboard translations [`5ac6fe3`](https://git.odit.services/lfk/frontend/commit/5ac6fe30b5b9e34043c734d51d5da137fdf7ac38) | ||||
| - feat(runners): Created_via filters can now be set via query params [`14501d3`](https://git.odit.services/lfk/frontend/commit/14501d3828dd0d48ba0baeeddf936ba275f7b9b7) | ||||
| - refactor(tools): Move tools to tools route [`16dc789`](https://git.odit.services/lfk/frontend/commit/16dc789db5d9ea41774c77622a579cc0d9bd95f2) | ||||
| - refactor(tools): Move tools into shared directory instead of the non-descript "general" [`e4f9b1a`](https://git.odit.services/lfk/frontend/commit/e4f9b1a60551d7955def4d068d534cf17b1ea640) | ||||
|  | ||||
| #### [1.12.7](https://git.odit.services/lfk/frontend/compare/1.12.6...1.12.7) | ||||
|  | ||||
| > 1 May 2025 | ||||
|  | ||||
| - chore(release): 1.12.7 [`c78bdfa`](https://git.odit.services/lfk/frontend/commit/c78bdfa5e24ada4909455064dd6b05cf34fc6df3) | ||||
| - fix(deps): fresh lockfile [`b2ed2af`](https://git.odit.services/lfk/frontend/commit/b2ed2afd8a45a1a01ac6118b27941e3b4b3b611f) | ||||
| - refactor(store): update refresh interval from 2min to 60min [`00d1988`](https://git.odit.services/lfk/frontend/commit/00d198895e15174b70a8d229974b4baa7d0ed8fc) | ||||
|  | ||||
| #### [1.12.6](https://git.odit.services/lfk/frontend/compare/1.12.5...1.12.6) | ||||
|  | ||||
| > 1 May 2025 | ||||
|  | ||||
| - feat(pdfs): Experimental generation of large runner card files [`93422b9`](https://git.odit.services/lfk/frontend/commit/93422b97799c5e45c89acadd34f33b1a11b04617) | ||||
| - chore(release): 1.12.6 [`b5c079d`](https://git.odit.services/lfk/frontend/commit/b5c079da9a0545d146e9f3029a543e04c907add3) | ||||
|  | ||||
| #### [1.12.5](https://git.odit.services/lfk/frontend/compare/1.12.4...1.12.5) | ||||
|  | ||||
| > 1 May 2025 | ||||
|  | ||||
| - chore(release): 1.12.5 [`6dcfd9a`](https://git.odit.services/lfk/frontend/commit/6dcfd9a4fedd1e44894c9803482576bc650fb4db) | ||||
| - fix(locales): Fixed translation [`2139524`](https://git.odit.services/lfk/frontend/commit/21395241de4de8f3a6b8404758d09c01d8a6f95f) | ||||
| - feat(runners): Show total donations in runner detail [`f27c716`](https://git.odit.services/lfk/frontend/commit/f27c716296e228ecccbf500a21130f1bc47ea52d) | ||||
| - chore(deps): Bump @odit/lfk-client-js to 1.2.7 [`6d19199`](https://git.odit.services/lfk/frontend/commit/6d1919974aacd74a265cf9ce0c9ed501028f0aa3) | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|  | ||||
|   <body> | ||||
|     <span style="display: none; visibility: hidden" id="buildinfo" | ||||
|       >RELEASE_INFO-1.12.5-RELEASE_INFO</span | ||||
|       >RELEASE_INFO-1.12.8-RELEASE_INFO</span | ||||
|     > | ||||
|     <noscript>You need to enable JavaScript to run this app.</noscript> | ||||
|     <script src="/env.js"></script> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@odit/lfk-frontend", | ||||
|   "version": "1.12.5", | ||||
|   "version": "1.12.8", | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "i18n-order": "node order.js", | ||||
|   | ||||
							
								
								
									
										10
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -15,8 +15,8 @@ importers: | ||||
|         specifier: ^5.2.5 | ||||
|         version: 5.2.5 | ||||
|       '@odit/lfk-client-js': | ||||
|         specifier: 1.2.5 | ||||
|         version: 1.2.5 | ||||
|         specifier: 1.2.7 | ||||
|         version: 1.2.7 | ||||
|       '@paralleldrive/cuid2': | ||||
|         specifier: 2.2.2 | ||||
|         version: 2.2.2 | ||||
| @@ -491,8 +491,8 @@ packages: | ||||
|   '@octokit/types@13.10.0': | ||||
|     resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} | ||||
|  | ||||
|   '@odit/lfk-client-js@1.2.5': | ||||
|     resolution: {integrity: sha512-a5vwqpjFXB5cVOCmjC/tZVi9OXJS8aMesNidSqwK2cwA/oC5yTJAqxKXGDhq9k/JLLipVGDJdaKMYmYVzRWkgA==} | ||||
|   '@odit/lfk-client-js@1.2.7': | ||||
|     resolution: {integrity: sha512-sqbbTjGlalN32VPshXClR3qM0+TFgWCX9+2UCo7u/tABEIs7hsYTVXKSZ+fJNfAUCK6ZJiZV0ND6+Dcnk7s29A==} | ||||
|  | ||||
|   '@odit/license-exporter@0.2.0': | ||||
|     resolution: {integrity: sha512-RRyfQzDLoyLQlGSd8ThJQ3h0fiCe4tkmm935AUvSVQWP+p88FcnI4iaktKBJJVBnIpDhkv/7sDSA5dFc/QMM5w==} | ||||
| @@ -2412,7 +2412,7 @@ snapshots: | ||||
|     dependencies: | ||||
|       '@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': | ||||
|     dependencies: | ||||
|   | ||||
| @@ -41,7 +41,7 @@ | ||||
|   import Settings from "./components/settings/Settings.svelte"; | ||||
|   import Transition from "./components/base/Transition.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 Footer from "./components/general/Footer.svelte"; | ||||
|   import TracksOverview from "./components/tracks/TracksOverview.svelte"; | ||||
| @@ -70,7 +70,7 @@ | ||||
|   import Cards from "./components/cards/Cards.svelte"; | ||||
|   import StatsClients from "./components/statsclients/StatsClients.svelte"; | ||||
|   import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte"; | ||||
|   import CardReplacement from "./components/general/CardReplacement.svelte"; | ||||
|   import CardReplacement from "./components/tools/CardReplacement.svelte"; | ||||
|   store.init(); | ||||
| </script> | ||||
|  | ||||
| @@ -126,21 +126,19 @@ | ||||
|           <Route path="/:trackid" let:params /> | ||||
|         </Route> | ||||
|         <Route path="/runners/*"> | ||||
|           <Route path="/"> | ||||
|             <Runners created_via="all" /> | ||||
|           <Route path="/" let:meta> | ||||
|             <Runners created_via={meta.query.created_via} /> | ||||
|           </Route> | ||||
|           <Route path="/:runnerid" let:params> | ||||
|             <RunnerDetail {params} /> | ||||
|           </Route> | ||||
|         </Route> | ||||
|         <Route path="/cardassignment/*"> | ||||
|           <Route path="/"> | ||||
|             <CardAssignment /> | ||||
|         <Route path="/tools/*"> | ||||
|           <Route path="/cardassignment/"> | ||||
|               <CardAssignment /> | ||||
|           </Route> | ||||
|         </Route> | ||||
|         <Route path="/cardreplacement/*"> | ||||
|           <Route path="/"> | ||||
|             <CardReplacement /> | ||||
|           <Route path="/cardreplacement/"> | ||||
|               <CardReplacement /> | ||||
|           </Route> | ||||
|         </Route> | ||||
|         <Route path="/teams/*"> | ||||
|   | ||||
| @@ -1,479 +1,488 @@ | ||||
| <script> | ||||
| 	import { _ } from "svelte-i18n"; | ||||
| 	import localForage from "localforage"; | ||||
| 	import store from "../../store"; | ||||
| 	import { router } from "tinro"; | ||||
| 	import NoComponentLoaded from "../base/NoComponentLoaded.svelte"; | ||||
| 	import { AuthService } from "@odit/lfk-client-js"; | ||||
| 	import { Toaster } from "svelte-french-toast"; | ||||
| 	$: navOpen = false; | ||||
| 	function logout() { | ||||
| 		localForage.clear(); | ||||
| 		location.replace("/"); | ||||
| 	} | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import localForage from "localforage"; | ||||
|   import store from "../../store"; | ||||
|   import { router } from "tinro"; | ||||
|   import NoComponentLoaded from "../base/NoComponentLoaded.svelte"; | ||||
|   import { AuthService } from "@odit/lfk-client-js"; | ||||
|   import { Toaster } from "svelte-french-toast"; | ||||
|   $: navOpen = false; | ||||
|   function logout() { | ||||
|     localForage.clear(); | ||||
|     location.replace("/"); | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <section class="min-h-screen bg-gray-50"> | ||||
| 	<div | ||||
| 		class:collapsed_navigation={!navOpen} | ||||
| 		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" | ||||
| 	> | ||||
| 		<a href="/" class="flex items-center px-4 py-5"> | ||||
| 			<img src="/lfk-logo.png" alt="Logo" class="h-10" /> | ||||
| 			<h3 class="text-lg font-bold">LfK!Admin</h3> | ||||
| 		</a> | ||||
| 		<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation"> | ||||
| 			<a | ||||
| 				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" | ||||
| 				href="/" | ||||
| 			> | ||||
| 				<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 20 20" | ||||
| 					fill="currentColor" | ||||
| 				> | ||||
| 					<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" | ||||
| 					/> | ||||
| 				</svg> | ||||
| 				<span>{$_("dashboard-title")}</span> | ||||
| 			</a> | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET") && store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} | ||||
| 				<a | ||||
| 					class:activenav={$router.path.includes("/cardassignment/")} | ||||
| 					class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" | ||||
| 					href="/cardassignment/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						fill="currentColor" | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 					> | ||||
| 						<path | ||||
| 							fill-rule="evenodd" | ||||
| 							d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" | ||||
| 							clip-rule="evenodd" | ||||
| 						/> | ||||
| 					</svg> | ||||
|   <div | ||||
|     class:collapsed_navigation={!navOpen} | ||||
|     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" | ||||
|   > | ||||
|     <a href="/" class="flex items-center px-4 py-5"> | ||||
|       <img src="/lfk-logo.png" alt="Logo" class="h-10" /> | ||||
|       <h3 class="text-lg font-bold">LfK!Admin</h3> | ||||
|     </a> | ||||
|     <nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation"> | ||||
|       <a | ||||
|         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" | ||||
|         href="/" | ||||
|       > | ||||
|         <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 20 20" | ||||
|           fill="currentColor" | ||||
|         > | ||||
|           <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" | ||||
|           /> | ||||
|         </svg> | ||||
|         <span>{$_("dashboard-title")}</span> | ||||
|       </a> | ||||
|       <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase"> | ||||
|         {$_("quick-tools")} | ||||
|       </h2> | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET") && store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} | ||||
|         <a | ||||
|           class:activenav={$router.path.includes("/tools/cardassignment/")} | ||||
|           class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" | ||||
|           href="/tools/cardassignment/" | ||||
|         > | ||||
|           <svg | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|             fill="currentColor" | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|           > | ||||
|             <path | ||||
|               fill-rule="evenodd" | ||||
|               d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" | ||||
|               clip-rule="evenodd" | ||||
|             /> | ||||
|           </svg> | ||||
|  | ||||
| 					<span>{$_('card_assignment_menu')}</span> | ||||
| 				</a> | ||||
| 				<a | ||||
| 					class:activenav={$router.path.includes("/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" | ||||
| 					href="/cardreplacement/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						fill="currentColor" | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 					> | ||||
| 						<path | ||||
| 							fill-rule="evenodd" | ||||
| 							d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" | ||||
| 							clip-rule="evenodd" | ||||
| 						/> | ||||
| 					</svg> | ||||
|           <span>{$_("card_assignment_menu")}</span> | ||||
|         </a> | ||||
|         <a | ||||
|           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" | ||||
|           href="/tools/cardreplacement/" | ||||
|         > | ||||
|           <svg | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|             fill="currentColor" | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|           > | ||||
|             <path | ||||
|               fill-rule="evenodd" | ||||
|               d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" | ||||
|               clip-rule="evenodd" | ||||
|             /> | ||||
|           </svg> | ||||
|  | ||||
| 					<span>{$_('card-replacement-menu')}</span> | ||||
| 				</a> | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/runners/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<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" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("runners")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/teams/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 640 512" | ||||
| 						><path | ||||
| 							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" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("teams")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/orgs/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<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" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("orgs")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/donors/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<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" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("donors")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/donations/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<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" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("donations")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("TRACK:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/tracks/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 640 512" | ||||
| 						><path | ||||
| 							fill="currentColor" | ||||
| 							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 | ||||
| 					> | ||||
| 					<span>{$_("tracks")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/cards/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 					> | ||||
| 						<path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<path | ||||
| 							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" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("cards")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/scans/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<path | ||||
| 							fill="currentColor" | ||||
| 							d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>Scans</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/contacts/" | ||||
| 				> | ||||
| 					<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" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<path | ||||
| 							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>{$_("contacts")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")} | ||||
| 				<a | ||||
| 					class:activenav={$router.path.includes("/scanstations/")} | ||||
| 					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" | ||||
| 						fill="currentColor" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<path | ||||
| 							fill="currentColor" | ||||
| 							d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("scanstations")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/statsclients/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<path | ||||
| 							fill="currentColor" | ||||
| 							d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("statsclients")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("USER:GET")} | ||||
| 				<a | ||||
| 					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" | ||||
| 					href="/users/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 						<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" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("users")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:GET")} | ||||
| 				<a | ||||
| 					class:activenav={$router.path.includes("/groups/")} | ||||
| 					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/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 						fill="currentColor" | ||||
| 						width="24" | ||||
| 						height="24" | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 640 512" | ||||
| 						><path | ||||
| 							fill="currentColor" | ||||
| 							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" | ||||
| 						/></svg | ||||
| 					> | ||||
| 					<span>{$_("user-groups")}</span> | ||||
| 				</a> | ||||
| 			{/if} | ||||
| 			<a | ||||
| 				class:activenav={$router.path === "/settings/"} | ||||
| 				class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" | ||||
| 				href="/settings/" | ||||
| 			> | ||||
| 				<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 20 20" | ||||
| 					fill="currentColor" | ||||
| 				> | ||||
| 					<path | ||||
| 						fill-rule="evenodd" | ||||
| 						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" | ||||
| 						clip-rule="evenodd" | ||||
| 					/> | ||||
| 				</svg> | ||||
| 				<span>{$_("settings")}</span> | ||||
| 			</a> | ||||
| 			<a | ||||
| 				class:activenav={$router.path === "/about/"} | ||||
| 				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="/about/" | ||||
| 			> | ||||
| 				<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="none" | ||||
| 					stroke="currentColor" | ||||
| 					stroke-width="2" | ||||
| 					stroke-linecap="round" | ||||
| 					stroke-linejoin="round" | ||||
| 					viewBox="0 0 24 24" | ||||
| 					><circle cx="12" cy="12" r="10" /> | ||||
| 					<path d="M12 16v-4M12 8h.01" /></svg | ||||
| 				> | ||||
| 				<span>{$_("about")}</span> | ||||
| 			</a> | ||||
| 			<button | ||||
| 				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" | ||||
| 				on:click={() => { | ||||
| 					AuthService.authControllerLogout(); | ||||
| 					logout(); | ||||
| 				}} | ||||
| 			> | ||||
| 				<svg | ||||
| 					class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 					fill="currentColor" | ||||
| 					width="24" | ||||
| 					height="24" | ||||
| 					xmlns="http://www.w3.org/2000/svg" | ||||
| 					viewBox="0 0 24 24" | ||||
| 					><path fill="none" d="M0 0h24v24H0z" /> | ||||
| 					<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" | ||||
| 					/></svg | ||||
| 				> | ||||
| 				<span>{$_("logout")}</span> | ||||
| 			</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} | ||||
|           <span>{$_("card-replacement-menu")}</span> | ||||
|         </a> | ||||
|         <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase"> | ||||
|           {$_("management")} | ||||
|         </h2> | ||||
|         <a | ||||
|           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" | ||||
|           href="/runners/" | ||||
|         > | ||||
|           <svg | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <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" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("runners")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("TEAM:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/teams/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 640 512" | ||||
|             ><path | ||||
|               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" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("teams")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("ORGANIZATION:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/orgs/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <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" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("orgs")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/donors/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <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" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("donors")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/donations/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <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" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("donations")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("TRACK:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/tracks/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 640 512" | ||||
|             ><path | ||||
|               fill="currentColor" | ||||
|               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 | ||||
|           > | ||||
|           <span>{$_("tracks")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/cards/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|           > | ||||
|             <path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <path | ||||
|               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" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("cards")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/scans/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 24 24" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <path | ||||
|               fill="currentColor" | ||||
|               d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>Scans</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("CONTACT:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/contacts/" | ||||
|         > | ||||
|           <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" | ||||
|             viewBox="0 0 24 24" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <path | ||||
|               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>{$_("contacts")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")} | ||||
|         <a | ||||
|           class:activenav={$router.path.includes("/scanstations/")} | ||||
|           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" | ||||
|             fill="currentColor" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             viewBox="0 0 24 24" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <path | ||||
|               fill="currentColor" | ||||
|               d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("scanstations")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("STATSCLIENT:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/statsclients/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             viewBox="0 0 24 24" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <path | ||||
|               fill="currentColor" | ||||
|               d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("statsclients")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("USER:GET")} | ||||
|         <a | ||||
|           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" | ||||
|           href="/users/" | ||||
|         > | ||||
|           <svg | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             viewBox="0 0 24 24" | ||||
|             ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|             <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" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("users")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
|       {#if store.state.jwtinfo.userdetails.permissions.includes("USERGROUP:GET")} | ||||
|         <a | ||||
|           class:activenav={$router.path.includes("/groups/")} | ||||
|           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/" | ||||
|         > | ||||
|           <svg | ||||
|             class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|             fill="currentColor" | ||||
|             width="24" | ||||
|             height="24" | ||||
|             xmlns="http://www.w3.org/2000/svg" | ||||
|             viewBox="0 0 640 512" | ||||
|             ><path | ||||
|               fill="currentColor" | ||||
|               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" | ||||
|             /></svg | ||||
|           > | ||||
|           <span>{$_("user-groups")}</span> | ||||
|         </a> | ||||
|       {/if} | ||||
| 	  <h2 class="px-4 py-2 text-xs font-semibold text-gray-600 uppercase"> | ||||
| 		  {$_("system")} | ||||
|         </h2> | ||||
|       <a | ||||
|         class:activenav={$router.path === "/settings/"} | ||||
|         class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" | ||||
|         href="/settings/" | ||||
|       > | ||||
|         <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 20 20" | ||||
|           fill="currentColor" | ||||
|         > | ||||
|           <path | ||||
|             fill-rule="evenodd" | ||||
|             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" | ||||
|             clip-rule="evenodd" | ||||
|           /> | ||||
|         </svg> | ||||
|         <span>{$_("settings")}</span> | ||||
|       </a> | ||||
|       <a | ||||
|         class:activenav={$router.path === "/about/"} | ||||
|         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="/about/" | ||||
|       > | ||||
|         <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="none" | ||||
|           stroke="currentColor" | ||||
|           stroke-width="2" | ||||
|           stroke-linecap="round" | ||||
|           stroke-linejoin="round" | ||||
|           viewBox="0 0 24 24" | ||||
|           ><circle cx="12" cy="12" r="10" /> | ||||
|           <path d="M12 16v-4M12 8h.01" /></svg | ||||
|         > | ||||
|         <span>{$_("about")}</span> | ||||
|       </a> | ||||
|       <button | ||||
|         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" | ||||
|         on:click={() => { | ||||
|           AuthService.authControllerLogout(); | ||||
|           logout(); | ||||
|         }} | ||||
|       > | ||||
|         <svg | ||||
|           class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
|           fill="currentColor" | ||||
|           width="24" | ||||
|           height="24" | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|           viewBox="0 0 24 24" | ||||
|           ><path fill="none" d="M0 0h24v24H0z" /> | ||||
|           <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" | ||||
|           /></svg | ||||
|         > | ||||
|         <span>{$_("logout")}</span> | ||||
|       </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> | ||||
|  | ||||
| <style> | ||||
| 	.collapsed_navigation { | ||||
| 		transform: translateX(-100%); | ||||
| 	} | ||||
| 	@media (min-width: 768px) { | ||||
| 		.collapsed_navigation { | ||||
| 			transform: translateX(0px); | ||||
| 		} | ||||
| 	} | ||||
|   .collapsed_navigation { | ||||
|     transform: translateX(-100%); | ||||
|   } | ||||
|   @media (min-width: 768px) { | ||||
|     .collapsed_navigation { | ||||
|       transform: translateX(0px); | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|   | ||||
| @@ -220,7 +220,7 @@ | ||||
| 			<StatCard | ||||
| 				title={$_("runner_via_selfservice")} | ||||
| 				value={stats.runnersViaSelfservice} | ||||
| 				href="/runners/" | ||||
| 				href="/runners/?created_via=selfservice" | ||||
| 			> | ||||
| 				<svg | ||||
| 					height="24" | ||||
| @@ -237,7 +237,7 @@ | ||||
| 			<StatCard | ||||
| 				title={$_('runners_via_kiosk')} | ||||
| 				value={stats.runnersViaKiosk} | ||||
| 				href="/runners/" | ||||
| 				href="/runners/?created_via=kiosk" | ||||
| 			> | ||||
| 				<svg | ||||
| 					height="24" | ||||
|   | ||||
| @@ -1,197 +1,256 @@ | ||||
| <script> | ||||
| 	import { _ } from "svelte-i18n"; | ||||
| 	import { | ||||
| 		RunnerCardService, | ||||
| 		RunnerOrganizationService, | ||||
| 		RunnerTeamService, | ||||
| 	} from "@odit/lfk-client-js"; | ||||
| 	import toast from "svelte-french-toast"; | ||||
| 	import DocumentServer from "./DocumentServer.ts"; | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import { | ||||
|     RunnerCardService, | ||||
|     RunnerOrganizationService, | ||||
|     RunnerTeamService, | ||||
|   } from "@odit/lfk-client-js"; | ||||
|   import toast from "svelte-french-toast"; | ||||
|   import DocumentServer from "./DocumentServer.ts"; | ||||
|  | ||||
| 	import { init } from "@paralleldrive/cuid2"; | ||||
| 	const createId = init({ length: 10, fingerprint: "lfk-frontend" }); | ||||
| 	const documentServer = new DocumentServer( | ||||
| 		config.baseurl_documentserver, | ||||
| 		config.documentserver_key | ||||
| 	); | ||||
|   import { init } from "@paralleldrive/cuid2"; | ||||
|   const createId = init({ length: 10, fingerprint: "lfk-frontend" }); | ||||
|   const documentServer = new DocumentServer( | ||||
|     config.baseurl_documentserver, | ||||
|     config.documentserver_key | ||||
|   ); | ||||
|  | ||||
| 	export let cards_show = false; | ||||
| 	export let generate_cards = []; | ||||
| 	export let generate_runners = []; | ||||
| 	export let generate_orgs = []; | ||||
| 	export let generate_teams = []; | ||||
|   export let cards_show = false; | ||||
|   export let generate_cards = []; | ||||
|   export let generate_runners = []; | ||||
|   export let generate_orgs = []; | ||||
|   export let generate_teams = []; | ||||
|  | ||||
| 	function download(blob, fileName) { | ||||
| 		const url = window.URL.createObjectURL(blob); | ||||
| 		let a = document.createElement("a"); | ||||
| 		a.href = url; | ||||
| 		a.download = fileName; | ||||
| 		document.body.appendChild(a); | ||||
| 		a.click(); | ||||
| 		a.remove(); | ||||
| 		toast.dismiss(); | ||||
| 		toast.success($_("pdf-successfully-generated")); | ||||
| 	} | ||||
|   function download(blob, fileName) { | ||||
|     const url = window.URL.createObjectURL(blob); | ||||
|     let a = document.createElement("a"); | ||||
|     a.href = url; | ||||
|     a.download = fileName; | ||||
|     document.body.appendChild(a); | ||||
|     a.click(); | ||||
|     a.remove(); | ||||
|     toast.dismiss(); | ||||
|     toast.success($_("pdf-successfully-generated")); | ||||
|   } | ||||
|  | ||||
| 	function generateRunnerCards(locale) { | ||||
| 		if (generate_orgs.length > 0) { | ||||
| 			generateOrgCards(locale); | ||||
| 		} else if (generate_teams.length > 0) { | ||||
| 			generateTeamCards(locale); | ||||
| 		} else if (generate_runners.length > 0) { | ||||
| 			generateRunnersCards(locale); | ||||
|   function generateRunnerCards(locale, useCombined = false) { | ||||
|     if (generate_orgs.length > 0) { | ||||
| 		if(useCombined){ | ||||
| 			generateOrgCardsCombined(locale); | ||||
| 		} 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) { | ||||
| 		toast.loading($_("generating-pdf")); | ||||
| 		documentServer | ||||
| 			.generateCards(generate_cards, locale) | ||||
| 			.then((blob) => { | ||||
| 				download(blob, `${$_("runnercards")}-${locale}-${createId()}.pdf`); | ||||
| 			}) | ||||
| 			.catch((err) => { | ||||
| 				console.error(err); | ||||
| 			}); | ||||
| 	} | ||||
|   function generateCards(locale) { | ||||
|     toast.loading($_("generating-pdf")); | ||||
|     documentServer | ||||
|       .generateCards(generate_cards, locale) | ||||
|       .then((blob) => { | ||||
|         download(blob, `${$_("runnercards")}-${locale}-${createId()}.pdf`); | ||||
|       }) | ||||
|       .catch((err) => { | ||||
|         console.error(err); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
| 	async function generateRunnersCards(locale) { | ||||
| 		toast.loading($_("generating-pdf")); | ||||
| 		const current_cards = await RunnerCardService.runnerCardControllerGetAll(); | ||||
| 		let cards = []; | ||||
| 		for (let runner of generate_runners) { | ||||
| 			let card = current_cards.find((c) => c.runner?.id == runner.id); | ||||
| 			if (!card) { | ||||
| 				card = await RunnerCardService.runnerCardControllerPost({ | ||||
| 					runner: runner.id, | ||||
| 				}); | ||||
| 			} | ||||
| 			cards.push(card); | ||||
| 		} | ||||
| 		documentServer | ||||
| 			.generateCards(cards, locale) | ||||
| 			.then((blob) => { | ||||
| 				let fileName = `${$_("runnercards")}-${locale}-${createId()}.pdf`; | ||||
| 				if (generate_runners.length == 1) { | ||||
| 					fileName = `${$_("runnercards")}_${generate_runners[0].firstname}_${ | ||||
| 						generate_runners[0].lastname | ||||
| 					}-${locale}-${createId()}.pdf`; | ||||
| 				} | ||||
| 				download(blob, fileName); | ||||
| 			}) | ||||
| 			.catch((err) => {}); | ||||
| 	} | ||||
|   async function generateRunnersCards(locale) { | ||||
|     toast.loading($_("generating-pdf")); | ||||
|     const current_cards = await RunnerCardService.runnerCardControllerGetAll(); | ||||
|     let cards = []; | ||||
|     for (let runner of generate_runners) { | ||||
|       let card = current_cards.find((c) => c.runner?.id == runner.id); | ||||
|       if (!card) { | ||||
|         card = await RunnerCardService.runnerCardControllerPost({ | ||||
|           runner: runner.id, | ||||
|         }); | ||||
|       } | ||||
|       cards.push(card); | ||||
|     } | ||||
|     documentServer | ||||
|       .generateCards(cards, locale) | ||||
|       .then((blob) => { | ||||
|         let fileName = `${$_("runnercards")}-${locale}-${createId()}.pdf`; | ||||
|         if (generate_runners.length == 1) { | ||||
|           fileName = `${$_("runnercards")}_${generate_runners[0].firstname}_${ | ||||
|             generate_runners[0].lastname | ||||
|           }-${locale}-${createId()}.pdf`; | ||||
|         } | ||||
|         download(blob, fileName); | ||||
|       }) | ||||
|       .catch((err) => {}); | ||||
|   } | ||||
|  | ||||
| 	async function generateTeamCards(locale) { | ||||
| 		toast.loading($_("generating-pdfs")); | ||||
| 		let count = 0; | ||||
| 		const current_cards = await RunnerCardService.runnerCardControllerGetAll(); | ||||
| 		for (const t of generate_teams) { | ||||
| 			const runners = await RunnerTeamService.runnerTeamControllerGetRunners( | ||||
| 				t.id | ||||
| 			); | ||||
| 			let cards = []; | ||||
| 			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); | ||||
| 			} | ||||
| 			documentServer | ||||
| 				.generateCards(cards, locale) | ||||
| 				.then((blob) => { | ||||
| 					download( | ||||
| 						blob, | ||||
| 						`${$_("runnercards")}_${t.name}-${locale}-${createId()}.pdf` | ||||
| 					); | ||||
| 				}) | ||||
| 				.catch((err) => {}); | ||||
| 		} | ||||
| 	} | ||||
|   async function generateTeamCards(locale) { | ||||
|     toast.loading($_("generating-pdfs")); | ||||
|     let count = 0; | ||||
|     const current_cards = await RunnerCardService.runnerCardControllerGetAll(); | ||||
|     for (const t of generate_teams) { | ||||
|       const runners = await RunnerTeamService.runnerTeamControllerGetRunners( | ||||
|         t.id | ||||
|       ); | ||||
|       let cards = []; | ||||
|       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); | ||||
|       } | ||||
|       documentServer | ||||
|         .generateCards(cards, locale) | ||||
|         .then((blob) => { | ||||
|           download( | ||||
|             blob, | ||||
|             `${$_("runnercards")}_${t.name}-${locale}-${createId()}.pdf` | ||||
|           ); | ||||
|         }) | ||||
|         .catch((err) => {}); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| 	async function generateOrgCards(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 count = 0; | ||||
| 			let runners = | ||||
| 				await RunnerOrganizationService.runnerOrganizationControllerGetRunners( | ||||
| 					o.id, | ||||
| 					true | ||||
| 				); | ||||
| 			let cards = []; | ||||
| 			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}_direct-${locale}-${createId()}.pdf` | ||||
| 					); | ||||
| 				}) | ||||
| 				.catch((err) => {}); | ||||
| 			for (const t of o.teams) { | ||||
| 				count++; | ||||
| 				let runners = await RunnerTeamService.runnerTeamControllerGetRunners( | ||||
| 					t.id | ||||
| 				); | ||||
| 				let cards = []; | ||||
| 				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}_${ | ||||
| 								t.name | ||||
| 							}-${locale}-${createId()}.pdf` | ||||
| 						); | ||||
| 					}) | ||||
| 					.catch((err) => {}); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   async function generateOrgCards(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 count = 0; | ||||
|       let runners = | ||||
|         await RunnerOrganizationService.runnerOrganizationControllerGetRunners( | ||||
|           o.id, | ||||
|           true | ||||
|         ); | ||||
|       let cards = []; | ||||
|       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}_direct-${locale}-${createId()}.pdf` | ||||
|           ); | ||||
|         }) | ||||
|         .catch((err) => {}); | ||||
|       for (const t of o.teams) { | ||||
|         count++; | ||||
|         let runners = await RunnerTeamService.runnerTeamControllerGetRunners( | ||||
|           t.id | ||||
|         ); | ||||
|         let cards = []; | ||||
|         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}_${ | ||||
|                 t.name | ||||
|               }-${locale}-${createId()}.pdf` | ||||
|             ); | ||||
|           }) | ||||
|           .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> | ||||
|  | ||||
| {#if cards_show} | ||||
| 	<button | ||||
| 		on:click={() => { | ||||
| 			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" | ||||
| 	> | ||||
| 		{$_("generate-runnercards")}: DE | ||||
| 	</button> | ||||
| 	<button | ||||
| 		on:click={() => { | ||||
| 			generateRunnerCards("en"); | ||||
| 		}} | ||||
| 		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> | ||||
|   <button | ||||
|     on:click={() => { | ||||
|       generateRunnerCards("de"); | ||||
|     }} | ||||
|     on:contextmenu|preventDefault={() => { | ||||
|       generateRunnerCards("de", true); | ||||
|     }} | ||||
|     class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0" | ||||
|   > | ||||
|     {$_("generate-runnercards")}: DE | ||||
|   </button> | ||||
|   <button | ||||
|     on:click={() => { | ||||
|       generateRunnerCards("en"); | ||||
|     }} | ||||
| 	on:contextmenu|preventDefault={() => { | ||||
|       generateRunnerCards("en", true); | ||||
|     }} | ||||
|     class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm mb-1 lg:mb-0" | ||||
|   > | ||||
|     {$_("generate-runnercards")}: EN | ||||
|   </button> | ||||
| {/if} | ||||
|   | ||||
| @@ -1,337 +1,342 @@ | ||||
| <script> | ||||
| 	import { | ||||
| 		RunnerOrganizationService, | ||||
| 		RunnerService, | ||||
| 		RunnerTeamService, | ||||
| 	} from "@odit/lfk-client-js"; | ||||
| 	import { | ||||
| 		createSvelteTable, | ||||
| 		flexRender, | ||||
| 		getCoreRowModel, | ||||
| 		getFilteredRowModel, | ||||
| 		getPaginationRowModel, | ||||
| 		getSortedRowModel, | ||||
| 		renderComponent, | ||||
| 	} from "@tanstack/svelte-table"; | ||||
| 	import { onMount } from "svelte"; | ||||
| 	import { writable } from "svelte/store"; | ||||
| 	import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; | ||||
| 	import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; | ||||
| 	import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; | ||||
| 	import InputElement from "../shared/InputElement.svelte"; | ||||
| 	import TableActions from "../shared/TableActions.svelte"; | ||||
| 	import { groupFilter } from "../shared/tablefilters"; | ||||
| 	import DeleteRunnerModal from "./DeleteRunnerModal.svelte"; | ||||
| 	import TableBottom from "../shared/TableBottom.svelte"; | ||||
| 	import TableHeader from "../shared/TableHeader.svelte"; | ||||
|   import { | ||||
|     RunnerOrganizationService, | ||||
|     RunnerService, | ||||
|     RunnerTeamService, | ||||
|   } from "@odit/lfk-client-js"; | ||||
|   import { | ||||
|     createSvelteTable, | ||||
|     flexRender, | ||||
|     getCoreRowModel, | ||||
|     getFilteredRowModel, | ||||
|     getPaginationRowModel, | ||||
|     getSortedRowModel, | ||||
|     renderComponent, | ||||
|   } from "@tanstack/svelte-table"; | ||||
|   import { onMount } from "svelte"; | ||||
|   import { writable } from "svelte/store"; | ||||
|   import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; | ||||
|   import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; | ||||
|   import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; | ||||
|   import InputElement from "../shared/InputElement.svelte"; | ||||
|   import TableActions from "../shared/TableActions.svelte"; | ||||
|   import { groupFilter } from "../shared/tablefilters"; | ||||
|   import DeleteRunnerModal from "./DeleteRunnerModal.svelte"; | ||||
|   import TableBottom from "../shared/TableBottom.svelte"; | ||||
|   import TableHeader from "../shared/TableHeader.svelte"; | ||||
|  | ||||
| 	$: selectedRunners = | ||||
| 		$table?.getSelectedRowModel().rows.map((row) => row.original) || []; | ||||
| 	$: selected = | ||||
| 		$table?.getSelectedRowModel().rows.map((row) => row.index) || []; | ||||
|   $: selectedRunners = | ||||
|     $table?.getSelectedRowModel().rows.map((row) => row.original) || []; | ||||
|   $: selected = | ||||
|     $table?.getSelectedRowModel().rows.map((row) => row.index) || []; | ||||
|  | ||||
| 	$: active_delete = undefined; | ||||
| 	let dataLoaded = false; | ||||
| 	export let created_via = "all"; | ||||
| 	export let current_runners = []; | ||||
| 	$: sponsoring_contracts_show = selected.length > 0; | ||||
| 	$: cards_show = selected.length > 0; | ||||
| 	$: certificates_show = selected.length > 0; | ||||
| 	$: teams = []; | ||||
| 	$: orgs = []; | ||||
|   $: active_delete = undefined; | ||||
|   let dataLoaded = false; | ||||
|   export let created_via = "all"; | ||||
|   export let current_runners = []; | ||||
|   $: sponsoring_contracts_show = selected.length > 0; | ||||
|   $: cards_show = selected.length > 0; | ||||
|   $: certificates_show = selected.length > 0; | ||||
|   $: teams = []; | ||||
|   $: orgs = []; | ||||
|  | ||||
| 	export const addRunners = (runners) => { | ||||
| 		current_runners = current_runners.concat(...runners); | ||||
| 		options.update((options) => ({ | ||||
| 			...options, | ||||
| 			data: current_runners, | ||||
| 		})); | ||||
| 	}; | ||||
|   export const addRunners = (runners) => { | ||||
|     current_runners = current_runners.concat(...runners); | ||||
|     options.update((options) => ({ | ||||
|       ...options, | ||||
|       data: current_runners, | ||||
|     })); | ||||
|   }; | ||||
|  | ||||
| 	//Section table | ||||
| 	const columns = [ | ||||
| 		{ | ||||
| 			accessorKey: "id", | ||||
| 			header: () => "id", | ||||
| 			filterFn: `equalsString`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "firstname", | ||||
| 			header: () => $_("first-name"), | ||||
| 			filterFn: `includesString`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "middlename", | ||||
| 			header: () => $_("middle-name"), | ||||
| 			cell: (info) => { | ||||
| 				if (!info || !info.getValue()) { | ||||
| 					return ""; | ||||
| 				} | ||||
| 				return info.getValue(); | ||||
| 			}, | ||||
| 			filterFn: `includesString`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "lastname", | ||||
| 			header: () => $_("last-name"), | ||||
| 			filterFn: `includesString`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "created_via", | ||||
| 			header: () => "created_via", | ||||
| 			filterFn: `includesString`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "group", | ||||
| 			header: () => $_("group"), | ||||
| 			cell: (info) => { | ||||
| 				const group = info.getValue(); | ||||
| 				if (group.responseType === "RUNNERORGANIZATION") { | ||||
| 					return group.name; | ||||
| 				} | ||||
| 				return `${group.parentGroup.name} > ${group.name}`; | ||||
| 			}, | ||||
| 			filterFn: `group`, | ||||
| 			sortingFn: (rowA, rowB, col) => { | ||||
| 				return rowA.original.group.name.localeCompare(rowB.original.group.name); | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "distance", | ||||
| 			header: () => $_("distance"), | ||||
| 			sortingFn: (rowA, rowB, col) => { | ||||
| 				return rowA.original.distance > rowB.original.distance; | ||||
| 			}, | ||||
| 			cell: (info) => { | ||||
| 				if (info.getValue() < 1000) { | ||||
| 					return `${info.getValue()} m`; | ||||
| 				} | ||||
| 				return `${(info.getValue() / 1000).toFixed(1)} km`; | ||||
| 			}, | ||||
| 			enableColumnFilter: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "actions", | ||||
| 			header: () => $_("action"), | ||||
| 			cell: (info) => { | ||||
| 				return renderComponent(TableActions, { | ||||
| 					detailsLink: `/runners/${info.row.original.id}`, | ||||
| 					deleteAction: () => { | ||||
| 						active_delete = | ||||
| 							current_runners[ | ||||
| 								current_runners.findIndex((r) => r.id == info.row.original.id) | ||||
| 							]; | ||||
| 					}, | ||||
| 					deleteEnabled: | ||||
| 						store.state.jwtinfo.userdetails.permissions.includes( | ||||
| 							"RUNNER:DELETE" | ||||
| 						), | ||||
| 				}); | ||||
| 			}, | ||||
| 			enableColumnFilter: false, | ||||
| 			enableSorting: false, | ||||
| 		}, | ||||
| 	]; | ||||
| 	const options = writable({ | ||||
| 		data: [], | ||||
| 		columns: columns, | ||||
| 		filterFns: { | ||||
| 			group: groupFilter, | ||||
| 		}, | ||||
| 		initialState: { | ||||
| 			pagination: { | ||||
| 				pageSize: 50, | ||||
| 			}, | ||||
| 		}, | ||||
| 		enableRowSelection: true, | ||||
| 		getCoreRowModel: getCoreRowModel(), | ||||
| 		getFilteredRowModel: getFilteredRowModel(), | ||||
| 		getPaginationRowModel: getPaginationRowModel(), | ||||
| 		getSortedRowModel: getSortedRowModel(), | ||||
| 	}); | ||||
| 	const table = createSvelteTable(options); | ||||
|   //Section table | ||||
|   const columns = [ | ||||
|     { | ||||
|       accessorKey: "id", | ||||
|       header: () => "id", | ||||
|       filterFn: `equalsString`, | ||||
|     }, | ||||
|     { | ||||
|       accessorKey: "firstname", | ||||
|       header: () => $_("first-name"), | ||||
|       filterFn: `includesString`, | ||||
|     }, | ||||
|     { | ||||
|       accessorKey: "middlename", | ||||
|       header: () => $_("middle-name"), | ||||
|       cell: (info) => { | ||||
|         if (!info || !info.getValue()) { | ||||
|           return ""; | ||||
|         } | ||||
|         return info.getValue(); | ||||
|       }, | ||||
|       filterFn: `includesString`, | ||||
|     }, | ||||
|     { | ||||
|       accessorKey: "lastname", | ||||
|       header: () => $_("last-name"), | ||||
|       filterFn: `includesString`, | ||||
|     }, | ||||
|     { | ||||
|       accessorKey: "created_via", | ||||
|       header: () => "created_via", | ||||
|       filterFn: `includesString`, | ||||
|     }, | ||||
|     { | ||||
|       accessorKey: "group", | ||||
|       header: () => $_("group"), | ||||
|       cell: (info) => { | ||||
|         const group = info.getValue(); | ||||
|         if (group.responseType === "RUNNERORGANIZATION") { | ||||
|           return group.name; | ||||
|         } | ||||
|         return `${group.parentGroup.name} > ${group.name}`; | ||||
|       }, | ||||
|       filterFn: `group`, | ||||
|       sortingFn: (rowA, rowB, col) => { | ||||
|         return rowA.original.group.name.localeCompare(rowB.original.group.name); | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       accessorKey: "distance", | ||||
|       header: () => $_("distance"), | ||||
|       sortingFn: (rowA, rowB, col) => { | ||||
|         return rowA.original.distance > rowB.original.distance; | ||||
|       }, | ||||
|       cell: (info) => { | ||||
|         if (info.getValue() < 1000) { | ||||
|           return `${info.getValue()} m`; | ||||
|         } | ||||
|         return `${(info.getValue() / 1000).toFixed(1)} km`; | ||||
|       }, | ||||
|       enableColumnFilter: false, | ||||
|     }, | ||||
|     { | ||||
|       accessorKey: "actions", | ||||
|       header: () => $_("action"), | ||||
|       cell: (info) => { | ||||
|         return renderComponent(TableActions, { | ||||
|           detailsLink: `/runners/${info.row.original.id}`, | ||||
|           deleteAction: () => { | ||||
|             active_delete = | ||||
|               current_runners[ | ||||
|                 current_runners.findIndex((r) => r.id == info.row.original.id) | ||||
|               ]; | ||||
|           }, | ||||
|           deleteEnabled: | ||||
|             store.state.jwtinfo.userdetails.permissions.includes( | ||||
|               "RUNNER:DELETE" | ||||
|             ), | ||||
|         }); | ||||
|       }, | ||||
|       enableColumnFilter: false, | ||||
|       enableSorting: false, | ||||
|     }, | ||||
|   ]; | ||||
|   const options = writable({ | ||||
|     data: [], | ||||
|     columns: columns, | ||||
|     filterFns: { | ||||
|       group: groupFilter, | ||||
|     }, | ||||
|     initialState: { | ||||
|       pagination: { | ||||
|         pageSize: 50, | ||||
|       }, | ||||
|     }, | ||||
|     enableRowSelection: true, | ||||
|     getCoreRowModel: getCoreRowModel(), | ||||
|     getFilteredRowModel: getFilteredRowModel(), | ||||
|     getPaginationRowModel: getPaginationRowModel(), | ||||
|     getSortedRowModel: getSortedRowModel(), | ||||
|   }); | ||||
|   const table = createSvelteTable(options); | ||||
|  | ||||
| 	async function deleteRunner(delete_runner_id) { | ||||
| 		await RunnerService.runnerControllerRemove(delete_runner_id, true); | ||||
| 		current_runners = current_runners.filter((r) => r.id !== delete_runner_id); | ||||
| 		options.update((options) => ({ | ||||
| 			...options, | ||||
| 			data: current_runners, | ||||
| 		})); | ||||
| 		toast.success($_("runner-deleted")); | ||||
| 	} | ||||
|   async function deleteRunner(delete_runner_id) { | ||||
|     await RunnerService.runnerControllerRemove(delete_runner_id, true); | ||||
|     current_runners = current_runners.filter((r) => r.id !== delete_runner_id); | ||||
|     options.update((options) => ({ | ||||
|       ...options, | ||||
|       data: current_runners, | ||||
|     })); | ||||
|     toast.success($_("runner-deleted")); | ||||
|   } | ||||
|  | ||||
| 	onMount(async () => { | ||||
| 		RunnerTeamService.runnerTeamControllerGetAll().then((val) => { | ||||
| 			teams = val; | ||||
| 		}); | ||||
| 		RunnerOrganizationService.runnerOrganizationControllerGetAll().then( | ||||
| 			(val) => { | ||||
| 				orgs = val; | ||||
| 			} | ||||
| 		); | ||||
|   onMount(async () => { | ||||
|     RunnerTeamService.runnerTeamControllerGetAll().then((val) => { | ||||
|       teams = val; | ||||
|     }); | ||||
|     RunnerOrganizationService.runnerOrganizationControllerGetAll().then( | ||||
|       (val) => { | ||||
|         orgs = val; | ||||
|       } | ||||
|     ); | ||||
|  | ||||
| 		let page = 0; | ||||
| 		while (page >= 0) { | ||||
| 			const runners = await RunnerService.runnerControllerGetAll( | ||||
| 				page, | ||||
| 				500, | ||||
| 				created_via | ||||
| 			); | ||||
| 			if (runners.length == 0) { | ||||
| 				page = -2; | ||||
| 			} | ||||
|     let page = 0; | ||||
|     while (page >= 0) { | ||||
|       const runners = await RunnerService.runnerControllerGetAll( | ||||
|         page, | ||||
|         500, | ||||
|       ); | ||||
|       if (runners.length == 0) { | ||||
|         page = -2; | ||||
|       } | ||||
|  | ||||
| 			current_runners = current_runners.concat(...runners); | ||||
| 			options.update((options) => ({ | ||||
| 				...options, | ||||
| 				data: current_runners, | ||||
| 			})); | ||||
|       current_runners = current_runners.concat(...runners); | ||||
|       options.update((options) => ({ | ||||
|         ...options, | ||||
|         data: current_runners, | ||||
|       })); | ||||
|  | ||||
| 			dataLoaded = true; | ||||
| 			page++; | ||||
| 		} | ||||
| 	}); | ||||
| 	import { _ } from "svelte-i18n"; | ||||
| 	import store from "../../store"; | ||||
| 	import AddRunnerModal from "./AddRunnerModal.svelte"; | ||||
| 	import ImportRunnerModal from "./ImportRunnerModal.svelte"; | ||||
| 	import toast from "svelte-french-toast"; | ||||
| 	$: current_runners = []; | ||||
| 	export let modal_open = false; | ||||
| 	export let import_modal_open = false; | ||||
|       dataLoaded = true; | ||||
|       page++; | ||||
|     } | ||||
|   }); | ||||
|   import { _ } from "svelte-i18n"; | ||||
|   import store from "../../store"; | ||||
|   import AddRunnerModal from "./AddRunnerModal.svelte"; | ||||
|   import ImportRunnerModal from "./ImportRunnerModal.svelte"; | ||||
|   import toast from "svelte-french-toast"; | ||||
|   $: current_runners = []; | ||||
|   export let modal_open = false; | ||||
|   export let import_modal_open = false; | ||||
|  | ||||
|   if (created_via != "all") { | ||||
|     $table.setColumnFilters([ | ||||
|       { | ||||
|         id: "created_via", | ||||
|         value: created_via, | ||||
|       }, | ||||
|     ]); | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <section class="container p-5"> | ||||
| 	<h4 class="mb-1 text-3xl font-extrabold leading-tight"> | ||||
| 		{$_("runners")} | ||||
| 	</h4> | ||||
| 	{#if created_via !== "all"} | ||||
| 		<p>created_via={created_via}</p> | ||||
| 	{/if} | ||||
| 	{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} | ||||
| 		<button | ||||
| 			on:click={() => { | ||||
| 				modal_open = true; | ||||
| 			}} | ||||
| 			type="button" | ||||
| 			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" | ||||
| 		> | ||||
| 			{$_("laeufer-hinzufuegen")} | ||||
| 		</button> | ||||
| 		<button | ||||
| 			on:click={() => { | ||||
| 				import_modal_open = true; | ||||
| 			}} | ||||
| 			type="button" | ||||
| 			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" | ||||
| 		> | ||||
| 			{$_("import-runners")} | ||||
| 		</button> | ||||
| 	{/if} | ||||
| 	<DeleteRunnerModal | ||||
| 		delete_runner={active_delete} | ||||
| 		modal_open={active_delete != undefined} | ||||
| 		on:delete={(event) => { | ||||
| 			deleteRunner(event.detail.id); | ||||
| 		}} | ||||
| 	/> | ||||
| 	{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")} | ||||
| 		{#if !dataLoaded} | ||||
| 			<div | ||||
| 				class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||
| 				role="alert" | ||||
| 			> | ||||
| 				<p class="font-bold">{$_("runners-are-being-loaded")}</p> | ||||
| 				<p class="text-sm">{$_("this-might-take-a-moment")}</p> | ||||
| 			</div> | ||||
| 		{:else} | ||||
| 			<GenerateSponsoringContracts | ||||
| 				bind:sponsoring_contracts_show | ||||
| 				bind:generate_runners={selectedRunners} | ||||
| 			/> | ||||
| 			<GenerateRunnerCards | ||||
| 				bind:cards_show | ||||
| 				bind:generate_runners={selectedRunners} | ||||
| 			/> | ||||
| 			<GenerateRunnerCertificates | ||||
| 				bind:certificates_show | ||||
| 				bind:generate_runners={selectedRunners} | ||||
| 			/> | ||||
| 			<div class="overflow-x-auto"> | ||||
| 				<table class="w-full"> | ||||
| 					<thead class="border-b border-gray-400"> | ||||
| 						{#each $table.getHeaderGroups() as headerGroup} | ||||
| 							<tr class="select-none"> | ||||
| 								<th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> | ||||
| 									<InputElement | ||||
| 										type="checkbox" | ||||
| 										checked={$table.getIsAllRowsSelected()} | ||||
| 										indeterminate={$table.getIsSomeRowsSelected()} | ||||
| 										on:change={() => $table.toggleAllRowsSelected()} | ||||
| 									/> | ||||
| 								</th> | ||||
| 								{#each headerGroup.headers as header} | ||||
| 									<TableHeader {header} /> | ||||
| 								{/each} | ||||
| 							</tr> | ||||
| 						{/each} | ||||
| 					</thead> | ||||
| 					<tbody> | ||||
| 						{#each $table.getRowModel().rows as row} | ||||
| 							<tr class="odd:bg-white even:bg-gray-100"> | ||||
| 								<td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> | ||||
| 									<InputElement | ||||
| 										type="checkbox" | ||||
| 										checked={row.getIsSelected()} | ||||
| 										on:change={() => row.toggleSelected()} | ||||
| 									/> | ||||
| 								</td> | ||||
| 								{#each row.getVisibleCells() as cell} | ||||
| 									<td> | ||||
| 										<svelte:component | ||||
| 											this={flexRender( | ||||
| 												cell.column.columnDef.cell, | ||||
| 												cell.getContext() | ||||
| 											)} | ||||
| 										/> | ||||
| 									</td> | ||||
| 								{/each} | ||||
| 							</tr> | ||||
| 						{/each} | ||||
| 					</tbody> | ||||
| 				</table> | ||||
| 			</div> | ||||
| 			<div class="h-2" /> | ||||
| 		{/if} | ||||
| 	{/if} | ||||
| 	<TableBottom {table} {selected} /> | ||||
|   <h4 class="mb-1 text-3xl font-extrabold leading-tight"> | ||||
|     {$_("runners")} | ||||
|   </h4> | ||||
|   {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} | ||||
|     <button | ||||
|       on:click={() => { | ||||
|         modal_open = true; | ||||
|       }} | ||||
|       type="button" | ||||
|       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" | ||||
|     > | ||||
|       {$_("laeufer-hinzufuegen")} | ||||
|     </button> | ||||
|     <button | ||||
|       on:click={() => { | ||||
|         import_modal_open = true; | ||||
|       }} | ||||
|       type="button" | ||||
|       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" | ||||
|     > | ||||
|       {$_("import-runners")} | ||||
|     </button> | ||||
|   {/if} | ||||
|   <DeleteRunnerModal | ||||
|     delete_runner={active_delete} | ||||
|     modal_open={active_delete != undefined} | ||||
|     on:delete={(event) => { | ||||
|       deleteRunner(event.detail.id); | ||||
|     }} | ||||
|   /> | ||||
|   {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")} | ||||
|     {#if !dataLoaded} | ||||
|       <div | ||||
|         class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" | ||||
|         role="alert" | ||||
|       > | ||||
|         <p class="font-bold">{$_("runners-are-being-loaded")}</p> | ||||
|         <p class="text-sm">{$_("this-might-take-a-moment")}</p> | ||||
|       </div> | ||||
|     {:else} | ||||
|       <GenerateSponsoringContracts | ||||
|         bind:sponsoring_contracts_show | ||||
|         bind:generate_runners={selectedRunners} | ||||
|       /> | ||||
|       <GenerateRunnerCards | ||||
|         bind:cards_show | ||||
|         bind:generate_runners={selectedRunners} | ||||
|       /> | ||||
|       <GenerateRunnerCertificates | ||||
|         bind:certificates_show | ||||
|         bind:generate_runners={selectedRunners} | ||||
|       /> | ||||
|       <div class="overflow-x-auto"> | ||||
|         <table class="w-full"> | ||||
|           <thead class="border-b border-gray-400"> | ||||
|             {#each $table.getHeaderGroups() as headerGroup} | ||||
|               <tr class="select-none"> | ||||
|                 <th class="inset-y-0 left-0 px-4 py-2 text-left w-px"> | ||||
|                   <InputElement | ||||
|                     type="checkbox" | ||||
|                     checked={$table.getIsAllRowsSelected()} | ||||
|                     indeterminate={$table.getIsSomeRowsSelected()} | ||||
|                     on:change={() => $table.toggleAllRowsSelected()} | ||||
|                   /> | ||||
|                 </th> | ||||
|                 {#each headerGroup.headers as header} | ||||
|                   <TableHeader {header} /> | ||||
|                 {/each} | ||||
|               </tr> | ||||
|             {/each} | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             {#each $table.getRowModel().rows as row} | ||||
|               <tr class="odd:bg-white even:bg-gray-100"> | ||||
|                 <td class="inset-y-0 left-0 px-4 py-2 text-center w-px"> | ||||
|                   <InputElement | ||||
|                     type="checkbox" | ||||
|                     checked={row.getIsSelected()} | ||||
|                     on:change={() => row.toggleSelected()} | ||||
|                   /> | ||||
|                 </td> | ||||
|                 {#each row.getVisibleCells() as cell} | ||||
|                   <td> | ||||
|                     <svelte:component | ||||
|                       this={flexRender( | ||||
|                         cell.column.columnDef.cell, | ||||
|                         cell.getContext() | ||||
|                       )} | ||||
|                     /> | ||||
|                   </td> | ||||
|                 {/each} | ||||
|               </tr> | ||||
|             {/each} | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
|       <div class="h-2" /> | ||||
|     {/if} | ||||
|   {/if} | ||||
|   <TableBottom {table} {selected} /> | ||||
| </section> | ||||
|  | ||||
| {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} | ||||
| 	<AddRunnerModal | ||||
| 		bind:modal_open | ||||
| 		on:created={(event) => { | ||||
| 			addRunners(event.detail.runners); | ||||
| 		}} | ||||
| 	/> | ||||
| 	<ImportRunnerModal | ||||
| 		on:cancelDelete={(event) => { | ||||
| 			import_modal_open = false; | ||||
| 		}} | ||||
| 		passed_team={{}} | ||||
| 		passed_orgs={[]} | ||||
| 		passed_org={{}} | ||||
| 		opened_from="RunnerOverview" | ||||
| 		bind:import_modal_open | ||||
| 		on:created={(event) => { | ||||
| 			addRunners(event.detail.runners); | ||||
| 		}} | ||||
| 	/> | ||||
|   <AddRunnerModal | ||||
|     bind:modal_open | ||||
|     on:created={(event) => { | ||||
|       addRunners(event.detail.runners); | ||||
|     }} | ||||
|   /> | ||||
|   <ImportRunnerModal | ||||
|     on:cancelDelete={(event) => { | ||||
|       import_modal_open = false; | ||||
|     }} | ||||
|     passed_team={{}} | ||||
|     passed_orgs={[]} | ||||
|     passed_org={{}} | ||||
|     opened_from="RunnerOverview" | ||||
|     bind:import_modal_open | ||||
|     on:created={(event) => { | ||||
|       addRunners(event.detail.runners); | ||||
|     }} | ||||
|   /> | ||||
| {/if} | ||||
|  | ||||
| <style> | ||||
| 	table tbody tr td:nth-child(2) { | ||||
| 		font-family: monospace; | ||||
| 	} | ||||
|   table tbody tr td:nth-child(2) { | ||||
|     font-family: monospace; | ||||
|   } | ||||
| </style> | ||||
|   | ||||
| @@ -296,6 +296,7 @@ | ||||
|     "logout": "Abmelden", | ||||
|     "mail-validation-in-progress": "E-Mail Verifizierung läuft... ", | ||||
|     "manage-admin-users": "Nutzer verwalten", | ||||
|     "management": "Verwaltung", | ||||
|     "middle-name": "Mittelname", | ||||
|     "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", | ||||
| @@ -375,6 +376,7 @@ | ||||
|     "profile-deleted": "Profil gelöscht!", | ||||
|     "profile-picture": "Profilbild", | ||||
|     "profile-updated": "Profil wurde aktualisiert!", | ||||
|     "quick-tools": "Werkzeuge", | ||||
|     "read-license": "Lizenz-Text lesen", | ||||
|     "receipt-needed": "Spendenquittung benötigt", | ||||
|     "repo_link": "Link", | ||||
| @@ -439,6 +441,7 @@ | ||||
|     "status": "Status", | ||||
|     "stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können", | ||||
|     "successful-password-reset": "Passwort erfolgreich zurückgesetzt!", | ||||
|     "system": "System", | ||||
|     "team": "Team", | ||||
|     "team-added": "Team wurde erstellt", | ||||
|     "team-deleted": "Team gelöscht", | ||||
|   | ||||
| @@ -51,7 +51,7 @@ | ||||
|     "author": "Author", | ||||
|     "available-permissions": "available", | ||||
|     "average-distance": "∅ distance", | ||||
|     "average-donation": "∅ donation", | ||||
|     "average-donation": "∅ Donation", | ||||
|     "barcode_scanner": "Scan via barcode scanner", | ||||
|     "by": "by", | ||||
|     "cancel": "Cancel", | ||||
| @@ -375,6 +375,7 @@ | ||||
|     "profile-deleted": "Profile deleted!", | ||||
|     "profile-picture": "Profile Picture", | ||||
|     "profile-updated": "Profile updated!", | ||||
|     "quick-tools": "Tools", | ||||
|     "read-license": "Read License", | ||||
|     "receipt-needed": "Receipt needed", | ||||
|     "repo_link": "Link", | ||||
| @@ -388,7 +389,7 @@ | ||||
|     "runner-is-being-added": "Runner is being added...", | ||||
|     "runner-updated": "Runner updated!", | ||||
|     "runner_not_found": "Runner not found...", | ||||
|     "runner_via_selfservice": "Runner via Selfservice", | ||||
|     "runner_via_selfservice": "Runners via Selfservice", | ||||
|     "runnercards": "Runnercards", | ||||
|     "runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"", | ||||
|     "runners": "Runners", | ||||
| @@ -439,6 +440,7 @@ | ||||
|     "status": "Status", | ||||
|     "stuff-that-could-harm-your-profile": "Stuff that could harm your profile", | ||||
|     "successful-password-reset": "Successful password reset!", | ||||
|     "system": "System", | ||||
|     "team": "Team", | ||||
|     "team-added": "Team added", | ||||
|     "team-deleted": "Team deleted", | ||||
| @@ -469,11 +471,11 @@ | ||||
|     "token": "Token", | ||||
|     "total-distance": "total distance", | ||||
|     "total-donation-amount": "Total donations", | ||||
|     "total-donation-count": "total donations (count)", | ||||
|     "total-donations": "total donations", | ||||
|     "total-donors": "total donors", | ||||
|     "total-donation-count": "Donations (count)", | ||||
|     "total-donations": "Donations (amount)", | ||||
|     "total-donors": "Donors", | ||||
|     "total-paid-amount": "Paid", | ||||
|     "total-scans": "total scans", | ||||
|     "total-scans": "Scans", | ||||
|     "total_donation_amount_in_eur": "Total donation amount in €", | ||||
|     "track": "Track", | ||||
|     "track-added": "Track added", | ||||
|   | ||||
| @@ -43,8 +43,8 @@ const store = () => { | ||||
|         // | ||||
|         state.refreshInterval = setInterval(() => { | ||||
|           this.refreshAuth(); | ||||
|           // 2min | ||||
|         }, 2 * 60000); | ||||
|           // 60min | ||||
|         }, 60 * 60000); | ||||
|         // | ||||
|         return state; | ||||
|       }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user