Compare commits

..

6 Commits

12 changed files with 835 additions and 807 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -296,6 +296,7 @@
"logout": "Abmelden", "logout": "Abmelden",
"mail-validation-in-progress": "E-Mail Verifizierung läuft... ", "mail-validation-in-progress": "E-Mail Verifizierung läuft... ",
"manage-admin-users": "Nutzer verwalten", "manage-admin-users": "Nutzer verwalten",
"management": "Verwaltung",
"middle-name": "Mittelname", "middle-name": "Mittelname",
"minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)", "minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)",
"minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein", "minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein",
@ -375,6 +376,7 @@
"profile-deleted": "Profil gelöscht!", "profile-deleted": "Profil gelöscht!",
"profile-picture": "Profilbild", "profile-picture": "Profilbild",
"profile-updated": "Profil wurde aktualisiert!", "profile-updated": "Profil wurde aktualisiert!",
"quick-tools": "Werkzeuge",
"read-license": "Lizenz-Text lesen", "read-license": "Lizenz-Text lesen",
"receipt-needed": "Spendenquittung benötigt", "receipt-needed": "Spendenquittung benötigt",
"repo_link": "Link", "repo_link": "Link",
@ -439,6 +441,7 @@
"status": "Status", "status": "Status",
"stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können", "stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können",
"successful-password-reset": "Passwort erfolgreich zurückgesetzt!", "successful-password-reset": "Passwort erfolgreich zurückgesetzt!",
"system": "System",
"team": "Team", "team": "Team",
"team-added": "Team wurde erstellt", "team-added": "Team wurde erstellt",
"team-deleted": "Team gelöscht", "team-deleted": "Team gelöscht",

View File

@ -51,7 +51,7 @@
"author": "Author", "author": "Author",
"available-permissions": "available", "available-permissions": "available",
"average-distance": "∅ distance", "average-distance": "∅ distance",
"average-donation": "∅ donation", "average-donation": "∅ Donation",
"barcode_scanner": "Scan via barcode scanner", "barcode_scanner": "Scan via barcode scanner",
"by": "by", "by": "by",
"cancel": "Cancel", "cancel": "Cancel",
@ -375,6 +375,7 @@
"profile-deleted": "Profile deleted!", "profile-deleted": "Profile deleted!",
"profile-picture": "Profile Picture", "profile-picture": "Profile Picture",
"profile-updated": "Profile updated!", "profile-updated": "Profile updated!",
"quick-tools": "Tools",
"read-license": "Read License", "read-license": "Read License",
"receipt-needed": "Receipt needed", "receipt-needed": "Receipt needed",
"repo_link": "Link", "repo_link": "Link",
@ -388,7 +389,7 @@
"runner-is-being-added": "Runner is being added...", "runner-is-being-added": "Runner is being added...",
"runner-updated": "Runner updated!", "runner-updated": "Runner updated!",
"runner_not_found": "Runner not found...", "runner_not_found": "Runner not found...",
"runner_via_selfservice": "Runner via Selfservice", "runner_via_selfservice": "Runners via Selfservice",
"runnercards": "Runnercards", "runnercards": "Runnercards",
"runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"", "runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"",
"runners": "Runners", "runners": "Runners",
@ -439,6 +440,7 @@
"status": "Status", "status": "Status",
"stuff-that-could-harm-your-profile": "Stuff that could harm your profile", "stuff-that-could-harm-your-profile": "Stuff that could harm your profile",
"successful-password-reset": "Successful password reset!", "successful-password-reset": "Successful password reset!",
"system": "System",
"team": "Team", "team": "Team",
"team-added": "Team added", "team-added": "Team added",
"team-deleted": "Team deleted", "team-deleted": "Team deleted",
@ -469,11 +471,11 @@
"token": "Token", "token": "Token",
"total-distance": "total distance", "total-distance": "total distance",
"total-donation-amount": "Total donations", "total-donation-amount": "Total donations",
"total-donation-count": "total donations (count)", "total-donation-count": "Donations (count)",
"total-donations": "total donations", "total-donations": "Donations (amount)",
"total-donors": "total donors", "total-donors": "Donors",
"total-paid-amount": "Paid", "total-paid-amount": "Paid",
"total-scans": "total scans", "total-scans": "Scans",
"total_donation_amount_in_eur": "Total donation amount in €", "total_donation_amount_in_eur": "Total donation amount in €",
"track": "Track", "track": "Track",
"track-added": "Track added", "track-added": "Track added",