Compare commits

...

51 Commits

Author SHA1 Message Date
2316baa898 Added check if key exists 2021-03-26 18:03:58 +01:00
f185d559c0 Formatting
ref #112
2021-03-26 18:01:34 +01:00
73d95bc004 Fixed changes in wrong file
ref #112
2021-03-26 18:01:17 +01:00
fcd55f89d7 You can now copy the selfservice links to your clipboard
ref #112
2021-03-26 17:59:46 +01:00
f9fe793573 Added checkbox to enable registration
ref #112
2021-03-26 17:37:54 +01:00
bc36411993 Merge branch 'main' into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 16:17:51 +00:00
48506236bf Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 17:15:51 +01:00
ded9b589fe new license file version [CI SKIP] 2021-03-26 16:16:10 +00:00
67c3732fad 🚀RELEASE v0.9.0 2021-03-26 17:15:42 +01:00
2932f4591e Merge pull request 'Runner cards feature/94-runnercard_mgnt' (#111) from feature/94-runnercard_mgnt into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #111
2021-03-26 16:14:45 +00:00
df53c07450 CardsOverview - move to 'enabled' language key
ref #94
2021-03-26 17:13:55 +01:00
40899e9d80 drop console log - CardDetailModal
ref #94
2021-03-26 17:13:38 +01:00
f794af0950 ✒ typo - "Geb" -> "Gebe"
ref #94
2021-03-26 17:01:40 +01:00
1665a1a093 Sorted translations 🌍
ref #94
2021-03-26 16:22:10 +01:00
4a36fb6d95 Added card generation/printing from detail
ref #94
2021-03-26 16:21:46 +01:00
acf78a8822 Added a new runenrcard logo
ref #94
2021-03-26 16:12:40 +01:00
f5c1ec9939 Fixed counting bug
ref #94
2021-03-26 16:05:49 +01:00
4b3d38b05b Now with working org runenrcard generation
ref #94
2021-03-26 16:05:39 +01:00
23e0b53107 Added runnercard generation for teams
ref #94
2021-03-26 15:58:39 +01:00
c907486c4d Working runner runnercard generation
ref #94
2021-03-26 15:53:04 +01:00
6b5945add8 Added translations
ref #94
2021-03-26 15:34:38 +01:00
55693de934 Removed debug info
ref #94
2021-03-26 15:34:01 +01:00
d467475b6d Basic card generation worX 🎉🎉
ref #94
2021-03-26 15:32:27 +01:00
44bc14820f Fuggin snowpack bs
ref #94
2021-03-26 14:47:56 +01:00
ec447e2e36 Merge branch 'dev' into feature/94-runnercard_mgnt 2021-03-25 20:30:31 +01:00
9f7d2234fb Formatting
ref #94
2021-03-25 20:22:01 +01:00
ddd82a71a7 Moved the pdf generation related componenets to their own folder
ref #94
2021-03-25 20:20:21 +01:00
014ba3bf67 Teams now use the new sponsoring contracts module
ref #94
2021-03-25 20:17:48 +01:00
c87321f804 Fixed org generation not hiding the generation toast
ref #94
2021-03-25 20:12:32 +01:00
8b451b3c67 Orgs now use the new sponsoring contracts module
ref #94
2021-03-25 20:09:43 +01:00
0cfc87fbe6 Moved contract generation to it's own component
ref #94
2021-03-25 20:06:35 +01:00
ae9673070c Now w/ working dialog🎉🎉🎉
ref #94
2021-03-25 19:14:15 +01:00
1a52aaf8d1 Moved modal import to overview for simplification
ref #94
2021-03-25 18:55:16 +01:00
d6c315ab8e Sorted translations 🌍
ref #94
2021-03-25 18:39:03 +01:00
983ce56048 Merge branch 'dev' into feature/94-runnercard_mgnt
# Conflicts:
#	src/locales/de.json
#	src/locales/en.json
2021-03-25 18:38:31 +01:00
de2fe0e9f1 Sorted translations
ref #94
2021-03-25 18:36:51 +01:00
fac059f02c Now w/working editing
ref #94
2021-03-24 16:58:06 +01:00
0313f8cc49 Added runnercard detail/edit modal
ref #94
2021-03-24 16:43:05 +01:00
7ad6b73574 Implemented bulk creation
ref #94
2021-03-23 19:55:55 +01:00
3cd0468b19 Bumped lfk client lib version
ref #94
2021-03-23 18:57:13 +01:00
f46ccb610e Added bulk creation modal to cards view
ref #94
2021-03-23 18:41:00 +01:00
8a32569a3b Added bulk card creation modal
ref #94
2021-03-23 18:35:21 +01:00
535b23ae91 Implemented Add card modal
ref #94
2021-03-23 17:58:13 +01:00
4715978f81 Added message for missing runner/blanco card)
ref #94
2021-03-23 17:39:14 +01:00
a516aa7775 Formatting
ref #94
2021-03-23 17:34:25 +01:00
77e9c205f9 Now importing runner overview
ref #94
2021-03-23 17:34:01 +01:00
e852305400 Now routing the cards page
ref #94
2021-03-23 17:31:11 +01:00
c6a15264b3 Added basic card overview
ref #94
2021-03-23 17:29:21 +01:00
2d0beaaaad Added CardsEmptyState + Emtystate graphic
ref #94
2021-03-23 17:19:10 +01:00
5c5ef95d2b Added basic cards page
ref #94
2021-03-23 17:13:31 +01:00
9aa8e7edff Merge pull request 'first merge to main 🚀' (#71) from dev into main
Reviewed-on: #71
Reviewed-by: Nicolai Ort <info@nicolai-ort.com>
2021-02-19 17:03:02 +00:00
24 changed files with 4303 additions and 3324 deletions

View File

@@ -2,8 +2,53 @@
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.
#### [0.9.0](https://git.odit.services/lfk/frontend/compare/0.8.7...0.9.0)
- Merge pull request 'Runner cards feature/94-runnercard_mgnt' (#111) from feature/94-runnercard_mgnt into dev [`2932f45`](https://git.odit.services/lfk/frontend/commit/2932f4591e62187a4903511051d110e9679c0993)
- Sorted translations 🌍 [`1665a1a`](https://git.odit.services/lfk/frontend/commit/1665a1a093862a13be78ec65dcdf64eb7d855593)
- Added translations [`6b5945a`](https://git.odit.services/lfk/frontend/commit/6b5945add86a77630c500872545bb91724b2047f)
- Sorted translations 🌍 [`d6c315a`](https://git.odit.services/lfk/frontend/commit/d6c315ab8e020bc65b967e2c3f4cd921392d66d5)
- Sorted translations [`de2fe0e`](https://git.odit.services/lfk/frontend/commit/de2fe0e9f171efb3deeea8cfe638f60e3ca90423)
- Added basic cards page [`5c5ef95`](https://git.odit.services/lfk/frontend/commit/5c5ef95d2be65c0e951dcd472113c8ce0593c9e0)
- Moved contract generation to it's own component [`0cfc87f`](https://git.odit.services/lfk/frontend/commit/0cfc87fbe6adfacab5c2fab732866aead3231fbf)
- Teams now use the new sponsoring contracts module [`014ba3b`](https://git.odit.services/lfk/frontend/commit/014ba3bf6718ff28f35c67c8f732b53aae50723c)
- Basic card generation worX 🎉🎉 [`d467475`](https://git.odit.services/lfk/frontend/commit/d467475b6d61d50bec3a043ea8792533e8593df6)
- Orgs now use the new sponsoring contracts module [`8b451b3`](https://git.odit.services/lfk/frontend/commit/8b451b3c6794e7df09898a687533ce8fadd56192)
- Added runnercard detail/edit modal [`0313f8c`](https://git.odit.services/lfk/frontend/commit/0313f8cc495088df1237d00e6b9ed1a94f019644)
- Implemented Add card modal [`535b23a`](https://git.odit.services/lfk/frontend/commit/535b23ae917de154e08962f5d486c50d6e091fe0)
- Added bulk card creation modal [`8a32569`](https://git.odit.services/lfk/frontend/commit/8a32569a3be1ad26ba163f4e2b67a368cfeeb422)
- Added basic card overview [`c6a1526`](https://git.odit.services/lfk/frontend/commit/c6a15264b3d13d516f3d97ea4b891ed1c328cead)
- Fixed org generation not hiding the generation toast [`c87321f`](https://git.odit.services/lfk/frontend/commit/c87321f804858f84fcccd85a15b9c3fb003c18be)
- Working runner runnercard generation [`c907486`](https://git.odit.services/lfk/frontend/commit/c907486c4d1c64114124deb3cd0d0cf11d38a6b1)
- Implemented bulk creation [`7ad6b73`](https://git.odit.services/lfk/frontend/commit/7ad6b73574174f24f2d6f23b3caf4823881a85e7)
- Now w/ working dialog🎉🎉🎉 [`ae96730`](https://git.odit.services/lfk/frontend/commit/ae9673070c3959ff6190a37123f3fc609b182c5a)
- Now w/working editing [`fac059f`](https://git.odit.services/lfk/frontend/commit/fac059f02cae84261443ee95448ec8db06dd755a)
- Added runnercard generation for teams [`23e0b53`](https://git.odit.services/lfk/frontend/commit/23e0b53107623c505d07a99a51ce836c9324acce)
- Added bulk creation modal to cards view [`f46ccb6`](https://git.odit.services/lfk/frontend/commit/f46ccb610e01654a4ee5e47d78ab500045dd494b)
- Added a new runenrcard logo [`acf78a8`](https://git.odit.services/lfk/frontend/commit/acf78a88221d0988f6501ae341e028a4113b578d)
- Moved modal import to overview for simplification [`1a52aaf`](https://git.odit.services/lfk/frontend/commit/1a52aaf8d1ad19b03d355aec0e1c48182db024f9)
- Added CardsEmptyState + Emtystate graphic [`2d0beaa`](https://git.odit.services/lfk/frontend/commit/2d0beaaaad4efefd036bbef09f10c8c22bdb2760)
- Added message for missing runner/blanco card) [`4715978`](https://git.odit.services/lfk/frontend/commit/4715978f810bbb283876f06d147b1ec86d373786)
- Fixed counting bug [`f5c1ec9`](https://git.odit.services/lfk/frontend/commit/f5c1ec9939d856804c9ec3ead4b3ed869fc2ea63)
- Added card generation/printing from detail [`4a36fb6`](https://git.odit.services/lfk/frontend/commit/4a36fb6d952d9fe4d5edbe1ed0779c7fbcd50ef0)
- Formatting [`a516aa7`](https://git.odit.services/lfk/frontend/commit/a516aa7775faa2244862bb2e3c4de623c6405e5b)
- Moved the pdf generation related componenets to their own folder [`ddd82a7`](https://git.odit.services/lfk/frontend/commit/ddd82a71a7b67ead892626addfd56ba4cc632750)
- Now routing the cards page [`e852305`](https://git.odit.services/lfk/frontend/commit/e852305400a139f8169350077c30012aed556828)
- Fuggin snowpack bs [`44bc148`](https://git.odit.services/lfk/frontend/commit/44bc14820fed26d5e0d8b12ecd6b46ca0608ae7b)
- Formatting [`9f7d223`](https://git.odit.services/lfk/frontend/commit/9f7d2234fb9603a7391ec9a64253724c2c25c333)
- Now with working org runenrcard generation [`4b3d38b`](https://git.odit.services/lfk/frontend/commit/4b3d38b05b3ed74fc3c0d77e00fa2ed245e6325c)
- Now importing runner overview [`77e9c20`](https://git.odit.services/lfk/frontend/commit/77e9c205f94cf56c2e3584444899adb1e8bdf3c6)
- CardsOverview - move to 'enabled' language key [`df53c07`](https://git.odit.services/lfk/frontend/commit/df53c0745035a220d4c07fdce1b5a5e4d763411d)
- ✒ typo - "Geb" -&gt; "Gebe" [`f794af0`](https://git.odit.services/lfk/frontend/commit/f794af0950de59a7a7b468c30abdcb5c145f65fb)
- Bumped lfk client lib version [`3cd0468`](https://git.odit.services/lfk/frontend/commit/3cd0468b1921824b131178cb02677540b079f9b0)
- drop console log - CardDetailModal [`40899e9`](https://git.odit.services/lfk/frontend/commit/40899e9d80ba07a3fbbcac72782db53d98dc318e)
- Removed debug info [`55693de`](https://git.odit.services/lfk/frontend/commit/55693de93420c2d76af296fcacc6bcad644a3cbf)
#### [0.8.7](https://git.odit.services/lfk/frontend/compare/0.8.6...0.8.7) #### [0.8.7](https://git.odit.services/lfk/frontend/compare/0.8.6...0.8.7)
> 25 March 2021
- 🚀RELEASE v0.8.7 [`0af2647`](https://git.odit.services/lfk/frontend/commit/0af26479656393b0baea88f6f83c778740a67e62)
- Fixed listen on wrong permission🐞 [`0844215`](https://git.odit.services/lfk/frontend/commit/08442154f4bf94fc1101808b4585dc1f95afe8b2) - Fixed listen on wrong permission🐞 [`0844215`](https://git.odit.services/lfk/frontend/commit/08442154f4bf94fc1101808b4585dc1f95afe8b2)
#### [0.8.6](https://git.odit.services/lfk/frontend/compare/0.8.5...0.8.6) #### [0.8.6](https://git.odit.services/lfk/frontend/compare/0.8.5...0.8.6)

View File

@@ -14,7 +14,7 @@
</head> </head>
<body> <body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.8.7-RELEASE_INFO</span> <span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.9.0-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>
<script defer type="module" src="/_dist_/index.js"></script> <script defer type="module" src="/_dist_/index.js"></script>

View File

@@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "0.8.7", "version": "0.9.0",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
"dev:all": "yarn prebuild && snowpack dev", "dev:all": "yarn prebuild && snowpack dev",
@@ -13,7 +13,7 @@
}, },
"license": "CC-BY-NC-SA-4.0", "license": "CC-BY-NC-SA-4.0",
"dependencies": { "dependencies": {
"@odit/lfk-client-js": "0.6.4", "@odit/lfk-client-js": "0.7.0",
"csvtojson": "^2.0.10", "csvtojson": "^2.0.10",
"gridjs": "3.3.0", "gridjs": "3.3.0",
"localforage": "1.9.0", "localforage": "1.9.0",

File diff suppressed because one or more lines are too long

View File

@@ -1,216 +1,224 @@
<script> <script>
import "./TailwindStyles.svelte"; import "./TailwindStyles.svelte";
import "toastify-js/src/toastify.css"; import "toastify-js/src/toastify.css";
import "gridjs/dist/theme/mermaid.css"; import "gridjs/dist/theme/mermaid.css";
import { Route, router } from "tinro"; import { Route, router } from "tinro";
router.subscribe((routeInfo) => { router.subscribe((routeInfo) => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
}); });
if (config.prefersHashRouting) { if (config.prefersHashRouting) {
if (config.prefersHashRouting === true) { if (config.prefersHashRouting === true) {
router.useHashNavigation(); router.useHashNavigation();
} }
} }
import localForage from "localforage"; import localForage from "localforage";
import { addMessages, init, getLocaleFromNavigator } from "svelte-i18n"; import { addMessages, init, getLocaleFromNavigator } from "svelte-i18n";
import en from "./locales/en.json"; import en from "./locales/en.json";
import de from "./locales/de.json"; import de from "./locales/de.json";
addMessages("en", en); addMessages("en", en);
addMessages("de", de); addMessages("de", de);
init({ init({
fallbackLocale: "en", fallbackLocale: "en",
initialLocale: getLocaleFromNavigator(), initialLocale: getLocaleFromNavigator(),
}); });
localForage.config({ localForage.config({
name: "lfk_admin", name: "lfk_admin",
version: 1.0, version: 1.0,
storeName: "lfk_admin", storeName: "lfk_admin",
description: "LfK! admin dashbaord", description: "LfK! admin dashbaord",
}); });
window.onunhandledrejection = (event) => { window.onunhandledrejection = (event) => {
if (event.reason.toString() == "Error: Unauthorized") { if (event.reason.toString() == "Error: Unauthorized") {
console.log("Found 1"); console.log("Found 1");
localForage.clear(); localForage.clear();
location.replace("/"); location.replace("/");
} }
}; };
// //
import Login from "./components/auth/Login.svelte"; import Login from "./components/auth/Login.svelte";
import Dashboard from "./components/dashboard/Dashboard.svelte"; import Dashboard from "./components/dashboard/Dashboard.svelte";
import store from "./store.js"; import store from "./store.js";
import ForgotPassword from "./components/auth/ForgotPassword.svelte"; import ForgotPassword from "./components/auth/ForgotPassword.svelte";
import MainDashContent from "./components/dashboard/MainDashContent.svelte"; import MainDashContent from "./components/dashboard/MainDashContent.svelte";
import Users from "./components/users/Users.svelte"; import Users from "./components/users/Users.svelte";
import About from "./components/general/About.svelte"; import About from "./components/general/About.svelte";
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 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";
import OrgDetail from "./components/orgs/OrgDetail.svelte"; import OrgDetail from "./components/orgs/OrgDetail.svelte";
import Teams from "./components/teams/Teams.svelte"; import Teams from "./components/teams/Teams.svelte";
import { OpenAPI } from "@odit/lfk-client-js"; import { OpenAPI } from "@odit/lfk-client-js";
import UserDetail from "./components/users/UserDetail.svelte"; import UserDetail from "./components/users/UserDetail.svelte";
OpenAPI.BASE = config.baseurl; OpenAPI.BASE = config.baseurl;
import { register as registerSW } from "./swmodule"; import { register as registerSW } from "./swmodule";
import TeamDetail from "./components/teams/TeamDetail.svelte"; import TeamDetail from "./components/teams/TeamDetail.svelte";
import UserPermissions from "./components/users/UserPermissions.svelte"; import UserPermissions from "./components/users/UserPermissions.svelte";
import GroupPermissions from "./components/groups/GroupPermissions.svelte"; import GroupPermissions from "./components/groups/GroupPermissions.svelte";
import RunnerDetail from "./components/runners/RunnerDetail.svelte"; import RunnerDetail from "./components/runners/RunnerDetail.svelte";
import Imprint from "./components/general/Imprint.svelte"; import Imprint from "./components/general/Imprint.svelte";
import Privacy from "./components/general/Privacy.svelte"; import Privacy from "./components/general/Privacy.svelte";
import ResetPassword from "./components/auth/ResetPassword.svelte"; import ResetPassword from "./components/auth/ResetPassword.svelte";
import Contacts from "./components/contacts/Contacts.svelte"; import Contacts from "./components/contacts/Contacts.svelte";
import ContactDetail from "./components/contacts/ContactDetail.svelte"; import ContactDetail from "./components/contacts/ContactDetail.svelte";
import Donors from "./components/donors/Donors.svelte"; import Donors from "./components/donors/Donors.svelte";
import Groups from "./components/groups/Groups.svelte"; import Groups from "./components/groups/Groups.svelte";
import DonorDetail from "./components/donors/DonorDetail.svelte"; import DonorDetail from "./components/donors/DonorDetail.svelte";
import Donations from "./components/donations/Donations.svelte"; import Donations from "./components/donations/Donations.svelte";
import DonationDetail from "./components/donations/DonationDetail.svelte"; import DonationDetail from "./components/donations/DonationDetail.svelte";
import GroupDetail from "./components/groups/GroupDetail.svelte"; import GroupDetail from "./components/groups/GroupDetail.svelte";
import ScanStationsOverview from "./components/scanstations/ScanStationsOverview.svelte"; import ScanStations from "./components/scanstations/ScanStations.svelte";
import ScanStations from "./components/scanstations/ScanStations.svelte"; import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte";
import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte"; import Scans from "./components/scans/Scans.svelte";
import Scans from "./components/scans/Scans.svelte"; import ScanDetail from "./components/scans/ScanDetail.svelte";
import ScanDetail from "./components/scans/ScanDetail.svelte"; import Cards from "./components/cards/Cards.svelte";
store.init(); store.init();
registerSW(); registerSW();
</script> </script>
<Route> <Route>
{#if $router.path === '/forgot_password'} {#if $router.path === '/forgot_password'}
<Route path="/forgot_password"> <Route path="/forgot_password">
<ForgotPassword /> <ForgotPassword />
</Route> </Route>
{:else if $router.path.includes('/reset')} {:else if $router.path.includes('/reset')}
<Route path="/reset/:resetkey" let:params> <Route path="/reset/:resetkey" let:params>
<ResetPassword {params} /> <ResetPassword {params} />
</Route> </Route>
{:else if $router.path === '/about'} {:else if $router.path === '/about'}
<Route path="/about"> <Route path="/about">
<About /> <About />
</Route> </Route>
{:else if $router.path === '/imprint'} {:else if $router.path === '/imprint'}
<Route path="/imprint"> <Route path="/imprint">
<Imprint /> <Imprint />
</Route> </Route>
{:else if $router.path === '/privacy'} {:else if $router.path === '/privacy'}
<Route path="/privacy"> <Route path="/privacy">
<Privacy /> <Privacy />
</Route> </Route>
{:else if $store.isLoggedIn} {:else if $store.isLoggedIn}
<Dashboard> <Dashboard>
<Transition> <Transition>
<Route path="/"> <Route path="/">
<MainDashContent /> <MainDashContent />
</Route> </Route>
<Route path="/users/*"> <Route path="/users/*">
<Route path="/"> <Route path="/">
<Users /> <Users />
</Route> </Route>
<Route path="/:userid/*" let:params> <Route path="/:userid/*" let:params>
<Route path="/"> <Route path="/">
<UserDetail {params} /> <UserDetail {params} />
</Route> </Route>
<Route path="/permissions/"> <Route path="/permissions/">
<UserPermissions {params} /> <UserPermissions {params} />
</Route> </Route>
</Route> </Route>
</Route> </Route>
<Route path="/groups/*"> <Route path="/groups/*">
<Route path="/"> <Route path="/">
<Groups /> <Groups />
</Route> </Route>
<Route path="/:groupid/*" let:params> <Route path="/:groupid/*" let:params>
<Route path="/"> <Route path="/">
<GroupDetail {params} /> <GroupDetail {params} />
</Route> </Route>
<Route path="/permissions/"> <Route path="/permissions/">
<GroupPermissions {params} /> <GroupPermissions {params} />
</Route> </Route>
</Route> </Route>
</Route> </Route>
<Route path="/tracks/*"> <Route path="/tracks/*">
<Route path="/"> <Route path="/">
<TracksOverview /> <TracksOverview />
</Route> </Route>
<Route path="/:trackid" let:params /> <Route path="/:trackid" let:params />
</Route> </Route>
<Route path="/runners/*"> <Route path="/runners/*">
<Route path="/"> <Route path="/">
<Runners /> <Runners />
</Route> </Route>
<Route path="/:runnerid" let:params> <Route path="/:runnerid" let:params>
<RunnerDetail {params} /> <RunnerDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/teams/*"> <Route path="/teams/*">
<Route path="/"> <Route path="/">
<Teams /> <Teams />
</Route> </Route>
<Route path="/:teamid" let:params> <Route path="/:teamid" let:params>
<TeamDetail {params} /> <TeamDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/contacts/*"> <Route path="/contacts/*">
<Route path="/"> <Route path="/">
<Contacts /> <Contacts />
</Route> </Route>
<Route path="/:contact" let:params> <Route path="/:contact" let:params>
<ContactDetail {params} /> <ContactDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/orgs/*"> <Route path="/orgs/*">
<Route path="/"> <Route path="/">
<Orgs /> <Orgs />
</Route> </Route>
<Route path="/:orgid" let:params> <Route path="/:orgid" let:params>
<OrgDetail {params} /> <OrgDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/donors/*"> <Route path="/donors/*">
<Route path="/"> <Route path="/">
<Donors /> <Donors />
</Route> </Route>
<Route path="/:donorid" let:params> <Route path="/:donorid" let:params>
<DonorDetail {params} /> <DonorDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/donations/*"> <Route path="/donations/*">
<Route path="/"> <Route path="/">
<Donations /> <Donations />
</Route> </Route>
<Route path="/:donationid" let:params> <Route path="/:donationid" let:params>
<DonationDetail {params} /> <DonationDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/scans/*"> <Route path="/cards/*">
<Route path="/"> <Route path="/">
<Scans /> <Cards />
</Route> </Route>
<Route path="/:scanid" let:params> <!-- <Route path="/:scanid" let:params>
<ScanDetail {params} /> <ScanDetail {params} />
</Route> </Route> -->
</Route> </Route>
<Route path="/scanstations/*"> <Route path="/scans/*">
<Route path="/"> <Route path="/">
<ScanStations /> <Scans />
</Route> </Route>
<Route path="/:stationid" let:params> <Route path="/:scanid" let:params>
<ScanStationDetail {params} /> <ScanDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/about"> <Route path="/scanstations/*">
<About /> <Route path="/">
</Route> <ScanStations />
<Route path="/settings"> </Route>
<Settings /> <Route path="/:stationid" let:params>
</Route> <ScanStationDetail {params} />
</Transition> </Route>
<Footer /> </Route>
</Dashboard> <Route path="/about">
{:else} <About />
<Login /> </Route>
{/if} <Route path="/settings">
</Route> <Settings />
</Route>
</Transition>
<Footer />
</Dashboard>
{:else}
<Login />
{/if}
</Route>

View File

@@ -0,0 +1,158 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let bulk_modal_open;
export let current_cards;
function focus(el) {
el.focus();
}
$: card_count = 0;
$: is_card_count_valid = card_count > 0;
$: processed_last_submit = true;
$: createbtnenabled = is_card_count_valid;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
bulk_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("creating-blanco-cards"),
duration: -1,
}).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count)
.then((result) => {
bulk_modal_open = false;
//
Toastify({
text: $_("created-blanco-cards"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if bulk_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
bulk_modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-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
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>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-bulk-blanco-cards')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('just-enter-how-many-you-want-and-the-system-will-create-them')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="amount"
class="block text-sm font-medium text-gray-700">{$_('amount')}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_card_count_valid}
class:focus:border-red-500={!is_card_count_valid}
class:focus:ring-red-500={!is_card_count_valid}
bind:value={card_count}
type="number"
step="1"
name="amount"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="400" />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">{$_('cards')}</span>
</div>
{#if !is_card_count_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('you-must-create-at-least-one-card-or-cancel')}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
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:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
bulk_modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,170 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerCardService,
RunnerService,
ScanService,
} from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
export let modal_open;
export let current_cards;
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase());
function focus(el) {
el.focus();
}
$: runner = 0;
$: runners = [];
$: enabled = true;
$: processed_last_submit = true;
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
});
$: createbtnenabled = true;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("adding-card"),
duration: -1,
}).showToast();
let postdata = {
runner,
enabled,
};
RunnerCardService.runnerCardControllerPost(postdata)
.then((result) => {
runner = 0;
modal_open = false;
//
Toastify({
text: $_("card-added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_cards.push(result);
current_cards = current_cards;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-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
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>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-card')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('you-can-provide-a-runner-but-you-dont-have-to')}
{$_('if-you-want-to-create-multiple-blanco-cards-try-the-add-bulk-button')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')}
on:select={(selectedValue) => (runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)} />
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
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:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,186 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService, RunnerService } from "@odit/lfk-client-js";
import Select from "svelte-select";
import Toastify from "toastify-js";
export let edit_modal_open;
export let current_cards;
export let runner = {};
export let editable = {};
export let original_data = {};
const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase());
function focus(el) {
el.focus();
}
$: runners = [];
$: enabled = true;
$: processed_last_submit = true;
RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r };
});
});
$: createbtnenabled = !(
JSON.stringify(editable) === JSON.stringify(original_data)
);
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
edit_modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("updating-card"),
duration: -1,
}).showToast();
RunnerCardService.runnerCardControllerPut(original_data.id, editable)
.then((result) => {
let id = original_data.id;
runner = {};
editable = {};
original_data = {};
edit_modal_open = false;
//
Toastify({
text: $_("card-updated"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_cards[current_cards.findIndex((c) => c.id === id)] = result;
current_cards = current_cards;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if edit_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
edit_modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-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
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>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('edit-a-card')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('you-can-provide-a-runner-but-you-dont-have-to')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="runner"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterRunners(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')}
bind:selectedValue={runner}
on:select={(selectedValue) => (editable.runner = selectedValue.detail.value.id)}
on:clear={() => (editable.runner = null)} />
</div>
<div class="col-span-6">
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable.enabled = !editable.enabled;
}}
name="enabled"
type="checkbox"
checked={editable.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
{$_('this-card-is')}
{#if editable.enabled}
{$_('enabled')}
{:else}{$_('disabled')}{/if}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
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:ml-3 sm:w-auto sm:text-sm">
{$_('save-changes')}
</button>
<button
on:click={() => {
edit_modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,40 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddCardBulkModal from "./AddCardBulkModal.svelte";
import AddCardModal from "./AddCardModal.svelte";
import CardsOverview from "./CardsOverview.svelte";
$: current_cards = [];
export let modal_open = false;
export let bulk_modal_open = false;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('cards')}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD: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:ml-3 sm:w-auto sm:text-sm">
{$_('add-card')}
</button>
<button
on:click={() => {
bulk_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:ml-3 sm:w-auto sm:text-sm">
{$_('create-bulk-cards')}
</button>
{/if}
</span>
<CardsOverview bind:current_cards />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')}
<AddCardModal bind:current_cards bind:modal_open />
<AddCardBulkModal bind:current_cards bind:bulk_modal_open />
{/if}

View File

@@ -0,0 +1,12 @@
<script>
import { _ } from "svelte-i18n";
import cards_empty from "./cards.svg";
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="m-auto" style="height:15rem" src={cards_empty} alt="" />
<span class="font-bold">{$_('there-are-no-cards-yet')}</span><br />
<span>{$_('add-your-first-card')}</span>
</p>
</div>

View File

@@ -0,0 +1,237 @@
<script>
import { getLocaleFromNavigator, json, _ } from "svelte-i18n";
import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store";
import Toastify from "toastify-js";
import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
export let edit_modal_open = false;
export let runner = {};
export let editable = {};
export let original_data = {};
export let current_cards = [];
$: searchvalue = "";
$: active_deletes = [];
$: cards_show = current_cards.some(
(r) => r.is_selected === true
);
$: generate_cards = current_cards.filter((r) => r.is_selected === true);
const cards_promise = RunnerCardService.runnerCardControllerGetAll().then(
(val) => {
current_cards = val;
}
);
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
const getRunnerLabel = (option) =>
option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}");
function open_edit_modal(card) {
if(card.runner?.id){
runner = Object.assign(
{ runner },
{ label: getRunnerLabel(card.runner), value: card.runner }
);
card.runner = card.runner.id;
}
else{
card.runner=null;
runner = null
}
editable = Object.assign(editable, card);
original_data = Object.assign(original_data, card);
edit_modal_open = true;
}
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:UPDATE')}
<CardDetailModal
bind:current_cards
bind:edit_modal_open
bind:runner
bind:editable
bind:original_data />
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
{#await cards_promise}
<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">{$_('loading-cards')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_cards.length === 0}
<CardsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div class="h-12">
<GenerateRunnerCards
bind:cards_show
bind:generate_cards />
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span
on:click={() => {
const newstate = !current_cards.some((r) => r.is_selected === true);
current_cards = current_cards.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none">{#if current_cards.some((r) => r.is_selected === true)}
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('code')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('status')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_cards as card}
{#if card.code
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || card.runner?.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || card.runner?.middlename
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || card.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(card.id)}
<tr data-rowid="card_{card.id}">
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={card.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">{card.code}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if card.runner}
<a
href="../runners/{card.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname}
{card.runner.middlename || ''}
{card.runner.lastname}</a>
{:else}{$_('non-blanko')}{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if card.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span>
{/if}
</div>
</td>
{#if active_deletes[card.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[card.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerCardService.runnerCardControllerRemove(card.id, false).then(
(resp) => {
current_cards = current_cards.filter(
(obj) => obj.id !== card.id
);
Toastify({
text: $_('card-deleted'),
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
open_edit_modal(card);
}}
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')}
<button
on:click={() => {
active_deletes[card.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -0,0 +1 @@
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 653.9 247.6"><path d="M272 211l-53 12s-11-2-1-17l4-4 27-14v-2l-6-41-2-16 17-17 4-3 44 41v1l-19 33z" fill="#ffb7b7"/><path d="M253 198l-54 13a6 6 0 01-5-7 16 16 0 012-5 48 48 0 016-9l28-14-4-24-1-12-2-8-2-16 21-19 22 21 22 20-3 5-3 7-17 30-3 6z" fill="#ffb7b7"/><path d="M346 190s-20-1-28-15a24 24 0 01-3-14l-8-17-11-23-30-4-2 1-21 19-7 6-10 9-49 44a37 37 0 01-7 9 50 50 0 01-9 7c-10 5-24 9-44 7L10 248 0 176l89-29 131-86 89 23 41 58z" fill="#ffb7b7"/><path d="M648 0H275a5 5 0 00-5 5v221a5 5 0 005 6h373a5 5 0 006-6V5a5 5 0 00-6-5z" fill="#fff"/><path d="M648 0H275a5 5 0 00-5 5v221a5 5 0 005 6h373a5 5 0 006-6V5a5 5 0 00-6-5zm4 226a4 4 0 01-4 4H275a4 4 0 01-3-4V5a4 4 0 013-3h373a4 4 0 014 3z" fill="#3f3d56"/><path d="M312 30a9 9 0 119-9 9 9 0 01-9 9zm0-17a8 8 0 107 8 8 8 0 00-7-8z" fill="#6c63ff"/><path d="M297 21a8 8 0 016-8 8 8 0 100 16 8 8 0 01-6-8zM349 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM368 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM386 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM415 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM434 130a7 7 0 01-7-7v-20a7 7 0 0113 0v20a7 7 0 01-6 7zM452 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM481 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM499 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM518 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM546 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM565 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7zM583 130a7 7 0 01-7-7v-20a7 7 0 0114 0v20a7 7 0 01-7 7z" fill="#6c63ff"/><path d="M396 208h-99a5 5 0 110-10h99a5 5 0 010 10zM364 188h-35a5 5 0 110-10h35a5 5 0 110 10z" fill="#e6e6e6"/><path fill="#3f3d56" d="M271 46h381v2H271z"/><path opacity=".1" d="M228 203l-1-2 33-15 8-27-12-10 1-1 13 10-8 30-34 15zM196 199l-9 4-17 2a50 50 0 01-9 7l-16-1-26-1 88-74 18 4-29 59z"/><path d="M318 175l-8 1-47 4-29 1-38 18-9 4-70 8 95-81 11 2 20 5 22 5 18 1 24 1 20 1a13 13 0 0112 13c0 7-5 14-21 17z" fill="#ffb7b7"/><path d="M325 170s-7-2-9-9c-2-4-1-9 3-15l1 1c-3 6-4 10-3 14 2 5 9 7 9 7zM197 197l34-16v2l-33 16zM218 135l48-19v2l-41 16 35 6v2l-42-7z" opacity=".1"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,308 +1,327 @@
<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";
$: 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">
<nav <nav
class:-translate-x-full={!navOpen} class:-translate-x-full={!navOpen}
class:translate-x-0={navOpen} class:translate-x-0={navOpen}
class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 md:translate-x-0 bg-gray-50"> class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 md:translate-x-0 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">Lauf für Kaya! Admin</h3> <h3 class="text-lg">Lauf für Kaya! 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:bg-gray-100={$router.path === '/'} class:bg-gray-100={$router.path === '/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/"> href="/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 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('ORGANIZATION:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
<a <a
class:bg-gray-100={$router.path.includes('/orgs/')} class:bg-gray-100={$router.path.includes('/orgs/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/orgs/"> href="/orgs/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <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> 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> <span>{$_('orgs')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')}
<a <a
class:bg-gray-100={$router.path === '/users/'} class:bg-gray-100={$router.path === '/users/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/users/"> href="/users/">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path <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> 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> <span>{$_('users')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')}
<a <a
class:bg-gray-100={$router.path === '/groups/'} class:bg-gray-100={$router.path === '/groups/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/groups/"> href="/groups/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 640 512"><path
fill="currentColor" 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> 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> <span>{$_('user-groups')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
<a <a
class:bg-gray-100={$router.path === '/runners/'} class:bg-gray-100={$router.path === '/runners/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/runners/"> href="/runners/">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <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> 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> <span>{$_('runners')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
<a <a
class:bg-gray-100={$router.path === '/teams/'} class:bg-gray-100={$router.path === '/teams/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/teams/"> href="/teams/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 640 512"><path
fill="currentColor" 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> 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> <span>{$_('teams')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
<a <a
class:bg-gray-100={$router.path.includes('/donors/')} class:bg-gray-100={$router.path.includes('/donors/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donors/"> href="/donors/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg> d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
<span>{$_('donors')}</span> <span>{$_('donors')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
<a <a
class:bg-gray-100={$router.path.includes('/donations/')} class:bg-gray-100={$router.path.includes('/donations/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donations/"> href="/donations/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <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> 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> <span>{$_('donations')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')}
<a <a
class:bg-gray-100={$router.path === '/tracks/'} class:bg-gray-100={$router.path === '/tracks/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/tracks/"> href="/tracks/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 640 512"><path
fill="currentColor" 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> 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> <span>{$_('tracks')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
<a <a
class:bg-gray-100={$router.path === '/scans/'} class:bg-gray-100={$router.path === '/cards/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scans/"> href="/cards/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24">
<path <path fill="none" d="M0 0h24v24H0z" />
fill="currentColor" <path
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> fill="currentColor"
<span>Scans</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" /></svg>
</a> <span>{$_('cards')}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
class:bg-gray-100={$router.path === '/contacts/'} <a
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class:bg-gray-100={$router.path === '/scans/'}
href="/contacts/"> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<svg href="/scans/">
fill="currentColor" <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 24 24" width="24"
width="24" height="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> xmlns="http://www.w3.org/2000/svg"
<path viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg> <path
<span>{$_('contacts')}</span> fill="currentColor"
</a> d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg>
{/if} <span>Scans</span>
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')} </a>
<a {/if}
class:bg-gray-100={$router.path === '/scanstations/'} {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" <a
href="/scanstations/"> class:bg-gray-100={$router.path === '/contacts/'}
<svg class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" href="/contacts/">
fill="currentColor" <svg
width="24" fill="currentColor"
height="24" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"><path viewBox="0 0 24 24"
fill="none" width="24"
d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> <span>{$_('contacts')}</span>
<span>{$_('scanstations')}</span> </a>
</a> {/if}
{/if} {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')}
<a <a
class:bg-gray-100={$router.path === '/settings/'} class:bg-gray-100={$router.path === '/scanstations/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/settings/"> href="/scanstations/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 20 20" width="24"
fill="currentColor"> height="24"
<path viewBox="0 0 24 24"
fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg"><path
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" fill="none"
clip-rule="evenodd" /> d="M0 0h24v24H0z" />
</svg> <path
<span>{$_('settings')}</span> fill="currentColor"
</a> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
<a <span>{$_('scanstations')}</span>
class:bg-gray-100={$router.path === '/about/'} </a>
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" {/if}
href="/about/"> <a
<svg class:bg-gray-100={$router.path === '/settings/'}
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
xmlns="http://www.w3.org/2000/svg" href="/settings/">
fill="none" <svg
stroke="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
stroke-width="2" xmlns="http://www.w3.org/2000/svg"
stroke-linecap="round" viewBox="0 0 20 20"
stroke-linejoin="round" fill="currentColor">
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /> <path
<path d="M12 16v-4M12 8h.01" /></svg> fill-rule="evenodd"
<span>{$_('about')}</span> 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"
</a> clip-rule="evenodd" />
<span </svg>
tabindex="0" <span>{$_('settings')}</span>
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" </a>
on:click={() => { <a
AuthService.authControllerLogout(); class:bg-gray-100={$router.path === '/about/'}
logout(); class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
}}> href="/about/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" xmlns="http://www.w3.org/2000/svg"
width="24" fill="none"
height="24" stroke="currentColor"
xmlns="http://www.w3.org/2000/svg" stroke-width="2"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> stroke-linecap="round"
<path stroke-linejoin="round"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg> viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<span>{$_('logout')}</span> <path d="M12 16v-4M12 8h.01" /></svg>
</span> <span>{$_('about')}</span>
</nav> </a>
</nav> <span
<div class="ml-0 transition md:ml-60"> tabindex="0"
<header class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
on:click={() => { on:click={() => {
navOpen = true; AuthService.authControllerLogout();
}} logout();
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"> }}>
<button class="block btn btn-light md:hidden"> <svg
<span class="sr-only">Menu</span><svg class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
class="w-4 h-4" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" width="24"
viewBox="0 0 20 20" height="24"
fill="currentcolor"><path xmlns="http://www.w3.org/2000/svg"
fill-rule="evenodd" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" <path
clip-rule="evenodd" /></svg></button> 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>
</header> <span>{$_('logout')}</span>
<slot> </span>
<NoComponentLoaded /> </nav>
</slot> </nav>
</div> <div class="ml-0 transition md:ml-60">
<div <header
on:click={() => { on:click={() => {
navOpen = false; navOpen = true;
}} }}
class:hidden={!navOpen} class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden">
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" /> <button class="block btn btn-light md:hidden">
</section> <span class="sr-only">Menu</span><svg
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentcolor"><path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd" /></svg></button>
</header>
<slot>
<NoComponentLoaded />
</slot>
</div>
<div
on:click={() => {
navOpen = false;
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
</section>

View File

@@ -10,6 +10,9 @@
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte"; import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select"; import Select from "svelte-select";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import { tick } from "svelte";
$: delete_triggered = false; $: delete_triggered = false;
$: address_valid_or_none = $: address_valid_or_none =
(isAddress1Valid && iszipcodevalid && iscityvalid) || (isAddress1Valid && iszipcodevalid && iscityvalid) ||
@@ -18,6 +21,9 @@
let original = ""; let original = "";
let original_object = {}; let original_object = {};
let contacts = []; let contacts = [];
let valueCopy = null;
let areaDom;
let copied = false;
export let params; export let params;
$: editable = {}; $: editable = {};
$: contact = {}; $: contact = {};
@@ -26,7 +32,10 @@
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0; $: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0; $: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0; $: iscityvalid = editable.address?.city?.trim().length !== 0;
$: sponsoring_contracts_download_open = false; $: sponsoring_contracts_show = true;
$: cards_show = true;
$: generate_orgs = [original_object];
$: registrationLink = `${config.baseurl}/selfservice/register/${editable.registrationKey}`;
const getContactLabel = (option) => const getContactLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne( const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne(
@@ -60,14 +69,6 @@
}); });
let modal_open = false; let modal_open = false;
let delete_org = {}; let delete_org = {};
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
function deleteOrganization() { function deleteOrganization() {
RunnerOrganizationService.runnerOrganizationControllerRemove( RunnerOrganizationService.runnerOrganizationControllerRemove(
original_object.id, original_object.id,
@@ -102,6 +103,7 @@
postdata postdata
) )
.then((resp) => { .then((resp) => {
editable.registrationKey = resp.registrationKey;
original_object = Object.assign({}, editable); original_object = Object.assign({}, editable);
original = JSON.stringify(original_object); original = JSON.stringify(original_object);
Toastify({ Toastify({
@@ -114,58 +116,37 @@
} else { } else {
} }
} }
export let import_modal_open = false; async function copy() {
async function generateSponsoringContract(locale) { valueCopy = registrationLink;
sponsoring_contracts_download_open = false; await tick();
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners( areaDom.focus();
original_object.id areaDom.select();
); try {
const toast = Toastify({ const successful = document.execCommand("copy");
text: $_("generating-pdf"), if (!successful) {
duration: -1, throw new Error();
}).showToast();
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
} }
) Toastify({
.then((response) => { text: $_("copied-link-to-clipboard"),
if (response.status != "200") { duration: 500,
toast.hideToast(); backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
Toastify({ }).showToast();
text: $_("pdf-generation-failed"), copied = true;
duration: 3500, } catch (err) {
backgroundColor: Toastify({
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", text: $_("error-whyile-copying-to-clipboard"),
}).showToast(); duration: 500,
} else { backgroundColor:
return response.blob(); "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
} }).showToast();
}) }
.then((blob) => { // we can notifi by event or storage about copy status
const url = window.URL.createObjectURL(blob); valueCopy = null;
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + original_object.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} }
export let import_modal_open = false;
</script> </script>
{#if valueCopy != null}<textarea bind:this={areaDom}>{valueCopy}</textarea>{/if}
<ImportRunnerModal <ImportRunnerModal
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
import_modal_open = false; import_modal_open = false;
@@ -182,64 +163,10 @@
<div class="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{original_object.name} {original_object.name}
<span data-id="org_actions_${editable.id}"> <span data-id="org_actions_${editable.id}">
<div id="sponsoring:dropdown" class="relative inline-block"> <GenerateSponsoringContracts
<div> bind:sponsoring_contracts_show
<button bind:generate_orgs />
on:click={() => { <GenerateRunnerCards bind:cards_show bind:generate_orgs />
sponsoring_contracts_download_open = !sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contracts')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button <button
on:click={() => { on:click={() => {
@@ -382,99 +309,150 @@
on:select={(selectedValue) => (editable.contact = selectedValue.detail.value)} on:select={(selectedValue) => (editable.contact = selectedValue.detail.value)}
on:clear={() => (editable.contact = null)} /> on:clear={() => (editable.contact = null)} />
</div> </div>
<!-- --> <div>
<div class="flex items-start mt-2"> <div class="flex items-start mt-2">
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input <input
bind:checked={editable.address_checked} bind:checked={editable.registrationEnabled}
id="comments" id="comments"
name="comments" name="comments"
type="checkbox" type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label
for="comments"
class="font-medium text-gray-700">{$_('selfservice-registration')}</label>
</div>
</div> </div>
<div class="ml-3 text-sm"> <div>
<label {#if editable.registrationEnabled}
for="comments" <div class="text-sm w-full">
class="font-medium text-gray-700">{$_('address')}</label> <div on:click={copy} class="inline-flex w-full">
<p
name="token"
class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2">
{#if editable.registrationKey}
{registrationLink}
{:else}
You have to save your changes to generate a link.
{/if}
</p>
<div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg>
</div>
</div>
<p class="text-gray-500 text-xs">
{$_('click-to-copy-the-link-into-your-clipboard')}
</p>
</div>
{/if}
<!-- -->
<div>
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label
for="comments"
class="font-medium text-gray-700">{$_('address')}</label>
</div>
</div>
</div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_('apartment-suite-etc')}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700">{$_('city')}</label>
<input
autocomplete="off"
placeholder={$_('city')}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
{/if}
</div> </div>
</div> </div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_('apartment-suite-etc')}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700">{$_('city')}</label>
<input
autocomplete="off"
placeholder={$_('city')}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
{/if}
</section> </section>
{:else} {:else}
{#await promise} {#await promise}

View File

@@ -1,318 +1,212 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
let modal_open = false; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let delete_org = {}; let modal_open = false;
import { RunnerOrganizationService } from "@odit/lfk-client-js"; let delete_org = {};
import store from "../../store"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import OrgsEmptyState from "./OrgsEmptyState.svelte"; import store from "../../store";
import Toastify from "toastify-js"; import OrgsEmptyState from "./OrgsEmptyState.svelte";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; import Toastify from "toastify-js";
$: searchvalue = ""; import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
$: active_deletes = []; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: sponsoring_contracts_download_open = false; $: searchvalue = "";
export let current_organizations = []; $: active_deletes = [];
$: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true);
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then( $: cards_show = current_organizations.some((r) => r.is_selected === true);
(val) => { $: generate_orgs = current_organizations.filter((r) => r.is_selected === true);
current_organizations = val; export let current_organizations = [];
}
); const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
document.addEventListener("click", function (e) { current_organizations = val;
if ( }
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && );
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" </script>
) {
sponsoring_contracts_download_open = false; <ConfirmOrgDeletion
} on:cancelDelete={(event) => {
}); modal_open = false;
active_deletes[event.detail.id] = false;
async function generateSponsoringContract(locale) { }}
sponsoring_contracts_download_open = false; bind:modal_open
const orgs = current_organizations.filter((r) => r.is_selected === true); bind:delete_org />
const toast = Toastify({ {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
text: $_("generating-pdfs"), {#await promise}
duration: -1, <div
}).showToast(); class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
let count = 0; role="alert">
for await (const o of orgs) { <p class="font-bold">{$_('organizations-are-being-loaded')}</p>
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners( <p class="text-sm">{$_('this-might-take-a-moment')}</p>
o.id </div>
); {:then}
fetch( {#if current_organizations.length === 0}
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, <OrgsEmptyState />
{ {:else}
method: "POST", <input
headers: { type="search"
"Content-Type": "application/json", bind:value={searchvalue}
}, placeholder={$_('datatable.search')}
body: JSON.stringify(runners), aria-label={$_('datatable.search')}
} class="gridjs-input gridjs-search-input mb-4" />
) <div class="h-12">
.then((response) => { <GenerateSponsoringContracts
if (response.status != "200") { bind:sponsoring_contracts_show
toast.hideToast(); bind:generate_orgs />
Toastify({ <GenerateRunnerCards
text: $_("pdf-generation-failed"), bind:cards_show
duration: 3500, bind:generate_orgs />
backgroundColor: </div>
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", <div
}).showToast(); class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
} else { <table class="divide-y divide-gray-200 w-full">
return response.blob(); <thead class="bg-gray-50">
} <tr>
}) <th
.then((blob) => { scope="col"
count++; class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
const url = window.URL.createObjectURL(blob); <span
let a = document.createElement("a"); on:click={() => {
a.href = url; const newstate = !current_organizations.some((r) => r.is_selected === true);
a.download = "Sponsorings_" + o.name + ".pdf"; current_organizations = current_organizations.map((r) => {
document.body.appendChild(a); r.is_selected = newstate;
a.click(); return r;
a.remove(); });
if (count === orgs.length) { }}
toast.hideToast(); class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)}
Toastify({ {$_('deselect-all')}
text: $_("pdfs-successfully-generated"), {:else}{$_('select-all')}{/if}
duration: 3500, </span>
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", </th>
}).showToast(); <th
} scope="col"
}) class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
.catch((err) => {}); {$_('name')}
} </th>
} <th
</script> scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<ConfirmOrgDeletion {$_('address')}
on:cancelDelete={(event) => { </th>
modal_open = false; <th
active_deletes[event.detail.id] = false; scope="col"
}} class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
bind:modal_open {$_('contact')}
bind:delete_org /> </th>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} <th scope="col" class="relative px-6 py-3">
{#await promise} <span class="sr-only">{$_('action')}</span>
<div </th>
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" </tr>
role="alert"> </thead>
<p class="font-bold">{$_('organizations-are-being-loaded')}</p> <tbody class="divide-y divide-gray-200">
<p class="text-sm">{$_('this-might-take-a-moment')}</p> {#each current_organizations as o}
</div> {#if Object.values(o)
{:then} .toString()
{#if current_organizations.length === 0} .toLowerCase()
<OrgsEmptyState /> .includes(searchvalue)}
{:else} <tr data-rowid="org_{o.id}">
<input <td class="px-6 py-4 whitespace-nowrap">
type="search" <input
bind:value={searchvalue} bind:checked={o.is_selected}
placeholder={$_('datatable.search')} type="checkbox"
aria-label={$_('datatable.search')} class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
class="gridjs-input gridjs-search-input mb-4" /> </td>
<div class="h-12"> <td class="px-6 py-4 whitespace-nowrap">
{#if current_organizations.some((r) => r.is_selected === true)} <div class="flex items-center">
<div id="sponsoring:dropdown" class="relative inline-block"> <div class="ml-4">
<div> <div class="text-sm font-medium text-gray-900">
<button {o.name}
on:click={() => { </div>
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; </div>
}} </div>
type="button" </td>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" <td class="px-6 py-4 whitespace-nowrap">
id="options-menu" <div class="flex items-center">
aria-haspopup="true" <div class="ml-4">
aria-expanded="true"> <div class="text-sm font-medium text-gray-900">
{$_('generate-sponsoring-contracts')} {#if o.address.address1 !== null}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="-mr-1 ml-2 h-5 w-5"><path fill="none" d="M0 0h24v24H0z"/><path fill="currentColor" d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"/></svg> {o.address.address1}<br />
</button> {o.address.address2 || ''}<br />
</div> {o.address.postalcode}
{#if sponsoring_contracts_download_open} {o.address.city}
<div {o.address.country}
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" {/if}
id="sponsoring:dropdown:menu"> </div>
<div </div>
class="py-1" </div>
role="menu" </td>
aria-orientation="vertical" <td class="px-6 py-4 whitespace-nowrap">
aria-labelledby="options-menu"> <div class="flex items-center">
<span <div class="ml-4">
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> <div class="text-sm font-medium text-gray-900">
<button {#if o.contact}
on:click={() => { <a
generateSponsoringContract('de'); href="../contacts/{o.contact.id}"
}} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
type="submit" {o.contact.middlename || ''}
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" {o.contact.lastname}</a>
role="menuitem"> {:else}{$_('no-contact-specified')}{/if}
{$_('german')} </div>
</button> </div>
<button </div>
on:click={() => { </td>
generateSponsoringContract('en'); {#if active_deletes[o.id] === true}
}} <td
type="submit" class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" <button
role="menuitem"> on:click={() => {
{$_('english')} active_deletes[o.id] = false;
</button> }}
</div> tabindex="0"
</div> class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
{/if} <button
</div> on:click={() => {
{/if} RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
</div> .then((resp) => {
<div current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> Toastify({
<table class="divide-y divide-gray-200 w-full"> text: 'Organization deleted',
<thead class="bg-gray-50"> duration: 500,
<tr> backgroundColor:
<th 'linear-gradient(to right, #00b09b, #96c93d)',
scope="col" }).showToast();
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> })
<span .catch((err) => {
on:click={() => { modal_open = true;
const newstate = !current_organizations.some((r) => r.is_selected === true); delete_org = o;
current_organizations = current_organizations.map((r) => { });
r.is_selected = newstate; }}
return r; tabindex="0"
}); class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
}} </td>
class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)} {:else}
{$_('deselect-all')} <td
{:else}{$_('select-all')}{/if} class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
</span> <a
</th> href="./{o.id}"
<th class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
scope="col" {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <button
{$_('name')} on:click={() => {
</th> active_deletes[o.id] = true;
<th }}
scope="col" tabindex="0"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{$_('address')} {/if}
</th> </td>
<th {/if}
scope="col" </tr>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> {/if}
{$_('contact')} {/each}
</th> </tbody>
<th scope="col" class="relative px-6 py-3"> </table>
<span class="sr-only">{$_('action')}</span> </div>
</th> {/if}
</tr> {:catch error}
</thead> <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<tbody class="divide-y divide-gray-200"> <span class="inline-block align-middle mr-8">
{#each current_organizations as o} <b class="capitalize">{$_('general_promise_error')}</b>
{#if Object.values(o) {error}
.toString() </span>
.toLowerCase() </div>
.includes(searchvalue)} {/await}
<tr data-rowid="org_{o.id}"> {/if}
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={o.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{o.name}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.address.address1 !== null}
{o.address.address1}<br />
{o.address.address2 || ''}<br />
{o.address.postalcode}
{o.address.city}
{o.address.country}
{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.contact}
<a
href="../contacts/{o.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
{o.contact.middlename || ''}
{o.contact.lastname}</a>
{:else}{$_('no-contact-specified')}{/if}
</div>
</div>
</div>
</td>
{#if active_deletes[o.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[o.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
.then((resp) => {
current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
Toastify({
text: 'Organization deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_org = o;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{o.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
<button
on:click={() => {
active_deletes[o.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -0,0 +1,339 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import {
RunnerCardService,
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let cards_show = false;
export let generate_cards = [];
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: cards_dropdown_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "cards:dropdown" &&
e.target.parentNode?.parentNode?.id != "cards:dropdown:menu"
) {
cards_dropdown_open = false;
}
});
function generateRunnerCards(locale) {
cards_dropdown_open = false;
if (generate_orgs.length > 0) {
generateOrgCards(locale);
} else if (generate_teams.length > 0) {
generateTeamCards(locale);
} else if (generate_runners.length > 0) {
generateRunnersCards(locale);
} else {
generateCards(locale);
}
}
function generateCards(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(generate_cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Runnercards.pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
}
async function generateRunnersCards(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
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);
}
fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Runnercards.pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
async function generateTeamCards(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for await (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);
}
fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + t.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
async function generateOrgCards(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
let count = 0;
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
for await (const o of generate_orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.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);
}
fetch(
`${config.baseurl}/documents/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + o.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
</script>
{#if cards_show}
<div id="cards:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
cards_dropdown_open = !cards_dropdown_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-runnercards')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if cards_dropdown_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="cards:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateRunnerCards('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateRunnerCards('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}

View File

@@ -0,0 +1,251 @@
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { RunnerOrganizationService, RunnerTeamService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let sponsoring_contracts_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: sponsoring_contracts_download_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
if (generate_orgs.length > 0) {
generateOrgContracts(locale);
} else if (generate_teams.length > 0) {
generateTeamContracts(locale);
} else {
generateRunnerContracts(locale);
}
}
async function generateTeamContracts(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
for await (const t of generate_teams) {
count++;
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + t.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_teams.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
async function generateOrgContracts(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
for await (const o of generate_orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id
);
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + o.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
if (count === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
function generateRunnerContracts(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(generate_runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsoring.pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {
console.error(err);
});
}
</script>
{#if sponsoring_contracts_show}
<div id="sponsoring:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
sponsoring_contracts_download_open = !sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contracts')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}

View File

@@ -1,398 +1,297 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import store from "../../store"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import { import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
RunnerService, import store from "../../store";
RunnerTeamService, import {
RunnerOrganizationService, RunnerService,
} from "@odit/lfk-client-js"; RunnerTeamService,
import Toastify from "toastify-js"; RunnerOrganizationService,
import PromiseError from "../base/PromiseError.svelte"; } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail"; import Toastify from "toastify-js";
import Select from "svelte-select"; import PromiseError from "../base/PromiseError.svelte";
let data_loaded = false; import isEmail from "validator/es/lib/isEmail";
export let params; import Select from "svelte-select";
const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid); let data_loaded = false;
$: delete_triggered = false; export let params;
$: sponsoring_contracts_download_open = false; const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid);
$: original_data_pdf = {}; $: delete_triggered = false;
$: original_data = {}; $: original_data_pdf = {};
$: editable = {}; $: original_data = {};
$: group = {} $: editable = {};
$: changes_performed = !(JSON.stringify(original_data) == JSON.stringify(editable)); $: group = {};
$: isEmailValid = $: changes_performed = !(
(editable.email || "") === "" || JSON.stringify(original_data) == JSON.stringify(editable)
(editable.email && isEmail(editable.email || "")); );
$: isFirstnameValid = editable.firstname !== ""; $: isEmailValid =
$: isLastnameValid = editable.lastname !== ""; (editable.email || "") === "" ||
$: save_enabled = (editable.email && isEmail(editable.email || ""));
changes_performed && $: isFirstnameValid = editable.firstname !== "";
isFirstnameValid && $: isLastnameValid = editable.lastname !== "";
isLastnameValid && $: save_enabled =
isEmailValid && changes_performed &&
editable.group != null; isFirstnameValid &&
runner_promise.then((data) => { isLastnameValid &&
data_loaded = true; isEmailValid &&
original_data_pdf = Object.assign(original_data_pdf, data); editable.group != null;
data.group = data.group.id; $: sponsoring_contracts_show = true;
original_data = Object.assign(original_data, data); $: cards_show = true;
editable = Object.assign(editable, original_data); $: generate_runners = [original_data_pdf];
runner_promise.then((data) => {
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { data_loaded = true;
const orgs = val.map((r) => { original_data_pdf = Object.assign(original_data_pdf, data);
return { label: r.name, value: r }; data.group = data.group.id;
}); original_data = Object.assign(original_data, data);
groups = groups.concat(orgs); editable = Object.assign(editable, original_data);
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
const teams = val.map((r) => { RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
return { label: `${r.parentGroup.name} > ${r.name}`, value: r }; (val) => {
}); const orgs = val.map((r) => {
groups = groups.concat(teams); return { label: r.name, value: r };
group = groups.find(g => g.value.id == editable.group) });
}); groups = groups.concat(orgs);
}); RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
}); const teams = val.map((r) => {
document.addEventListener("click", function (e) { return { label: `${r.parentGroup.name} > ${r.name}`, value: r };
if ( });
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && groups = groups.concat(teams);
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" group = groups.find((g) => g.value.id == editable.group);
) { });
sponsoring_contracts_download_open = false; }
} );
}); });
let groups = []; let groups = [];
function submit() { function submit() {
if (data_loaded === true && save_enabled) { if (data_loaded === true && save_enabled) {
Toastify({ Toastify({
text: $_("updating-runner"), text: $_("updating-runner"),
duration: 2500, duration: 2500,
}).showToast(); }).showToast();
let postdata = {}; let postdata = {};
postdata = Object.assign(postdata, editable); postdata = Object.assign(postdata, editable);
RunnerService.runnerControllerPut(original_data.id, postdata) RunnerService.runnerControllerPut(original_data.id, postdata)
.then((resp) => { .then((resp) => {
Object.assign(original_data, editable); Object.assign(original_data, editable);
original_data = original_data; original_data = original_data;
Toastify({ Toastify({
text: $_("runner-updated"), text: $_("runner-updated"),
duration: 2500, duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) })
.catch((err) => {}); .catch((err) => {});
} else { } else {
} }
} }
function deleteRunner() { function deleteRunner() {
RunnerService.runnerControllerRemove(original_data.id, true) RunnerService.runnerControllerRemove(original_data.id, true)
.then((resp) => { .then((resp) => {
location.replace("./"); location.replace("./");
}) })
.catch((err) => {}); .catch((err) => {});
} }
function generateSponsoringContract(locale) { </script>
sponsoring_contracts_download_open = false;
const toast = Toastify({ {#await runner_promise}
text: $_("generating-pdf"), {$_('loading-runners')}
duration: -1, {:then}
}).showToast(); <section class="container p-5 select-none">
fetch( <div class="flex flex-row mb-4">
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, <div class="w-full">
{ <nav class="w-full flex">
method: "POST", <ol class="list-none flex flex-row items-center justify-start">
headers: { <li class="flex items-center">
"Content-Type": "application/json", <svg
}, xmlns="http://www.w3.org/2000/svg"
body: JSON.stringify([original_data_pdf]), viewBox="0 0 24 24"
} class="flex-shrink-0 w-5 h-5 mr-2"
) fill="currentColor"
.then((response) => { width="24"
if (response.status != "200") { height="24"><path fill="none" d="M0 0h24v24H0z" />
toast.hideToast(); <path
Toastify({ 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>
text: $_("pdf-generation-failed"), </li>
duration: 3500, <li class="flex items-center">
backgroundColor: <a class="mr-2" href="./">{$_('runners')}</a><svg
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", stroke="currentColor"
}).showToast(); fill="none"
} else { stroke-width="2"
return response.blob(); viewBox="0 0 24 24"
} stroke-linecap="round"
}) stroke-linejoin="round"
.then((blob) => { class="h-3 w-3 mr-2 stroke-current"
const url = window.URL.createObjectURL(blob); height="1em"
let a = document.createElement("a"); width="1em"
a.href = url; xmlns="http://www.w3.org/2000/svg"><line
a.download = x1="5"
"Sponsoring_" + y1="12"
original_data.firstname + x2="19"
(original_data.middlename || "") + y2="12" />
original_data.lastname + <polyline points="12 5 19 12 12 19" /></svg>
".pdf"; </li>
document.body.appendChild(a); <li class="flex items-center">
a.click(); <span class="mr-2">{original_data.firstname}
a.remove(); {original_data.middlename || ''}
toast.hideToast(); {original_data.lastname}</span>
Toastify({ </li>
text: $_("pdf-successfully-generated"), </ol>
duration: 3500, </nav>
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", </div>
}).showToast(); </div>
}) <div class="mb-8 text-3xl font-extrabold leading-tight">
.catch((err) => { {original_data.firstname}
console.error(err); {original_data.middlename || ''}
}); {original_data.lastname}
} <span data-id="runner_actions_${editable.id}">
</script> {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
{#if delete_triggered}
{#await runner_promise} <button
{$_('loading-runners')} on:click={deleteRunner}
{:then} class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button>
<section class="container p-5 select-none"> <button
<div class="flex flex-row mb-4"> on:click={() => {
<div class="w-full"> delete_triggered = !delete_triggered;
<nav class="w-full flex"> }}
<ol class="list-none flex flex-row items-center justify-start"> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
<li class="flex items-center"> {/if}
<svg <GenerateSponsoringContracts
xmlns="http://www.w3.org/2000/svg" bind:sponsoring_contracts_show
viewBox="0 0 24 24" bind:generate_runners />
class="flex-shrink-0 w-5 h-5 mr-2" <GenerateRunnerCards
fill="currentColor" bind:sponsoring_contracts_show
width="24" bind:generate_runners />
height="24"><path fill="none" d="M0 0h24v24H0z" /> {#if !delete_triggered}
<path <button
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> on:click={() => {
</li> delete_triggered = true;
<li class="flex items-center"> }}
<a class="mr-2" href="./">{$_('runners')}</a><svg type="button"
stroke="currentColor" class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-runner')}</button>
fill="none" {/if}
stroke-width="2" {/if}
viewBox="0 0 24 24" {#if !delete_triggered}
stroke-linecap="round" <button
stroke-linejoin="round" disabled={!save_enabled}
class="h-3 w-3 mr-2 stroke-current" class:opacity-50={!save_enabled}
height="1em" type="button"
width="1em" on:click={submit}
xmlns="http://www.w3.org/2000/svg"><line 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:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
x1="5" {/if}
y1="12" </span>
x2="19" </div>
y2="12" /> <!-- -->
<polyline points="12 5 19 12 12 19" /></svg> <div class="text-sm w-full">
</li> <label
<li class="flex items-center"> for="firstname"
<span class="mr-2">{original_data.firstname} class="font-medium text-gray-700">{$_('first-name')}</label>
{original_data.middlename || ''} <input
{original_data.lastname}</span> autocomplete="off"
</li> placeholder={$_('first-name')}
</ol> type="text"
</nav> class:border-red-500={!isFirstnameValid}
</div> class:focus:border-red-500={!isFirstnameValid}
</div> class:focus:ring-red-500={!isFirstnameValid}
<div class="mb-8 text-3xl font-extrabold leading-tight"> bind:value={editable.firstname}
{original_data.firstname} name="firstname"
{original_data.middlename || ''} class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{original_data.lastname} {#if !isFirstnameValid}
<span data-id="runner_actions_${editable.id}"> <span
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{#if delete_triggered} {$_('first-name-is-required')}
<button </span>
on:click={deleteRunner} {/if}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button> </div>
<button <div class="text-sm w-full">
on:click={() => { <label
delete_triggered = !delete_triggered; for="middlename"
}} class="font-medium text-gray-700">{$_('middle-name')}</label>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> <input
{/if} autocomplete="off"
<div id="sponsoring:dropdown" class="relative inline-block"> placeholder={$_('middle-name')}
<div> type="text"
<button bind:value={editable.middlename}
on:click={() => { name="middlename"
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
}} </div>
type="button" <div class="text-sm w-full">
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" <label
id="options-menu" for="lastname"
aria-haspopup="true" class="font-medium text-gray-700">{$_('last-name')}</label>
aria-expanded="true"> <input
{$_('generate-sponsoring-contract')} autocomplete="off"
<svg placeholder={$_('last-name')}
xmlns="http://www.w3.org/2000/svg" type="text"
width="24" bind:value={editable.lastname}
height="24" class:border-red-500={!isLastnameValid}
viewBox="0 0 24 24" class:focus:border-red-500={!isLastnameValid}
class="-mr-1 ml-2 h-5 w-5"><path class:focus:ring-red-500={!isLastnameValid}
fill="none" name="lastname"
d="M0 0h24v24H0z" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
<path {#if !isLastnameValid}
fill="currentColor" <span
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
</button> {$_('last-name-is-required')}
</div> </span>
{#if sponsoring_contracts_download_open} {/if}
<div </div>
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" <div class="text-sm w-full">
id="sponsoring:dropdown:menu"> <label
<div for="email"
class="py-1" class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
role="menu" <input
aria-orientation="vertical" autocomplete="off"
aria-labelledby="options-menu"> placeholder={$_('e-mail-adress')}
<span type="email"
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> bind:value={editable.email}
<button class:border-red-500={!isEmailValid}
on:click={() => { class:focus:border-red-500={!isEmailValid}
generateSponsoringContract('de'); class:focus:ring-red-500={!isEmailValid}
}} name="email"
type="submit" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" {#if !isEmailValid}
role="menuitem"> <span
{$_('german')} class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
</button> {$_('valid-email-is-required')}
<button </span>
on:click={() => { {/if}
generateSponsoringContract('en'); </div>
}} <div class="text-sm w-full">
type="submit" <label for="phone" class="font-medium text-gray-700">{$_('phone')}</label>
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" <input
role="menuitem"> autocomplete="off"
{$_('english')} placeholder={$_('phone')}
</button> type="tel"
</div> bind:value={editable.phone}
</div> name="phone"
{/if} class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div> </div>
{#if !delete_triggered} <div class="text-sm w-full">
<button <span class="font-medium text-gray-700">{$_('group')}</span>
on:click={() => { <Select
delete_triggered = true; containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
}} itemFilter={(label, filterText, option) => label
type="button" .toLowerCase()
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-runner')}</button> .includes(
{/if} filterText.toLowerCase()
{/if} ) || option.id.value
{#if !delete_triggered} .toString()
<button .startsWith(filterText.toLowerCase())}
disabled={!save_enabled} items={groups}
class:opacity-50={!save_enabled} showChevron={true}
type="button" placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
on:click={submit} noOptionsMessage={$_('no-organization-or-team-found')}
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:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> bind:selectedValue={group}
{/if} on:select={(selectedValue) => {
</span> editable.group = selectedValue.detail.value.id;
</div> }}
<!-- --> on:clear={() => (editable.group = null)} />
<div class="text-sm w-full"> </div>
<label <div class="text-sm w-full">
for="firstname" <span class="font-medium text-gray-700">{$_('distance')}</span>
class="font-medium text-gray-700">{$_('first-name')}</label> <br />
<input <span class="text-gray-700">{original_data.distance} km</span>
autocomplete="off" </div>
placeholder={$_('first-name')} </section>
type="text" {:catch error}
class:border-red-500={!isFirstnameValid} <PromiseError {error} />
class:focus:border-red-500={!isFirstnameValid} {/await}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="middlename"
class="font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_('middle-name')}
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="lastname"
class="font-medium text-gray-700">{$_('last-name')}</label>
<input
autocomplete="off"
placeholder={$_('last-name')}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="email"
class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label>
<input
autocomplete="off"
placeholder={$_('phone')}
type="tel"
bind:value={editable.phone}
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('group')}</span>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label
.toLowerCase()
.includes(
filterText.toLowerCase()
) || option.id.value.toString().startsWith(filterText.toLowerCase())}
items={groups}
showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
noOptionsMessage={$_('no-organization-or-team-found')}
bind:selectedValue={group}
on:select={(selectedValue) => {editable.group = selectedValue.detail.value.id}}
on:clear={() => (editable.group = null)} />
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('distance')}</span>
<br />
<span class="text-gray-700">{original_data.distance} km</span>
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

@@ -1,360 +1,256 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { import {
RunnerService, RunnerService,
RunnerTeamService, RunnerTeamService,
RunnerOrganizationService, RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import RunnersEmptyState from "./RunnersEmptyState.svelte"; import RunnersEmptyState from "./RunnersEmptyState.svelte";
import Select from "svelte-select"; import Select from "svelte-select";
import Toastify from "toastify-js"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
$: searchvalue = ""; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: active_deletes = []; $: searchvalue = "";
export let current_runners = []; $: active_deletes = [];
const runners_promise = RunnerService.runnerControllerGetAll().then((val) => { export let current_runners = [];
current_runners = val; const runners_promise = RunnerService.runnerControllerGetAll().then((val) => {
}); current_runners = val;
$: selectedFilter_teams = null; });
$: selectedFilter = null; $: selectedFilter_teams = null;
$: filter__teams = selectedFilter_teams || []; $: selectedFilter = null;
$: filter__orgs = selectedFilter || []; $: filter__teams = selectedFilter_teams || [];
$: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value); $: filter__orgs = selectedFilter || [];
$: sponsoring_contracts_download_open = false; $: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value);
$: teams = []; $: sponsoring_contracts_show = current_runners.some(
$: orgs = []; (r) => r.is_selected === true
$: mappedteams = teams.map(function (g) { );
return { value: g.id, label: g.parentGroup.name + " > " + g.name }; $: cards_show = current_runners.some(
}); (r) => r.is_selected === true
$: selectgroups = orgs );
.map(function (g) { $: generate_runners = current_runners.filter((r) => r.is_selected === true);
return { value: g.id, label: g.name }; $: teams = [];
}) $: orgs = [];
.concat(mappedteams); $: mappedteams = teams.map(function (g) {
document.addEventListener("click", function (e) { return { value: g.id, label: g.parentGroup.name + " > " + g.name };
if ( });
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && $: selectgroups = orgs
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" .map(function (g) {
) { return { value: g.id, label: g.name };
sponsoring_contracts_download_open = false; })
} .concat(mappedteams);
});
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val; teams = val;
}); });
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val; orgs = val;
}); });
function should_display_based_on_id(id) { function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") { if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", "")); return id.toString().startsWith(searchvalue.replace("*", ""));
} }
return id.toString() === searchvalue; return id.toString() === searchvalue;
} }
function generateSponsoringContract(locale) { </script>
sponsoring_contracts_download_open = false;
const toast = Toastify({ {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
text: $_("generating-pdf"), {#await runners_promise}
duration: -1, <div
}).showToast(); class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
fetch( role="alert">
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, <p class="font-bold">{$_('runners-are-being-loaded')}</p>
{ <p class="text-sm">{$_('this-might-take-a-moment')}</p>
method: "POST", </div>
headers: { {:then}
"Content-Type": "application/json", {#if current_runners.length === 0}
}, <RunnersEmptyState />
body: JSON.stringify( {:else}
current_runners.filter((r) => r.is_selected === true) <input
), type="search"
} bind:value={searchvalue}
) placeholder={$_('datatable.search')}
.then((response) => { aria-label={$_('datatable.search')}
if (response.status != "200") { class="gridjs-input gridjs-search-input mb-4" />
toast.hideToast(); <div class="block mb-6">
Toastify({ <label
text: $_("pdf-generation-failed"), for="country"
duration: 3500, class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label>
backgroundColor: <Select
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", on:select={(event) => {
}).showToast(); selectedFilter = event.detail;
} else { }}
return response.blob(); selectedValue={selectedFilter}
} placeholder={$_('filter-by-organization-team')}
}) containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
.then((blob) => { items={selectgroups}
const url = window.URL.createObjectURL(blob); isMulti={true} />
let a = document.createElement("a"); </div>
a.href = url; <div class="h-12">
a.download = "Sponsoring.pdf"; <GenerateSponsoringContracts
document.body.appendChild(a); bind:sponsoring_contracts_show
a.click(); bind:generate_runners />
a.remove(); <GenerateRunnerCards
toast.hideToast(); bind:cards_show
Toastify({ bind:generate_runners />
text: $_("pdf-successfully-generated"), </div>
duration: 3500, <div
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
}).showToast(); <table class="divide-y divide-gray-200 w-full">
}) <thead class="bg-gray-50">
.catch((err) => { <tr>
console.error(err); <th
}); scope="col"
} class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</script> <span
on:click={() => {
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} const newstate = !current_runners.some((r) => r.is_selected === true);
{#await runners_promise} current_runners = current_runners.map((r) => {
<div r.is_selected = newstate;
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" return r;
role="alert"> });
<p class="font-bold">{$_('runners-are-being-loaded')}</p> }}
<p class="text-sm">{$_('this-might-take-a-moment')}</p> class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)}
</div> {$_('deselect-all')}
{:then} {:else}{$_('select-all')}{/if}
{#if current_runners.length === 0} </span>
<RunnersEmptyState /> </th>
{:else} <th
<input scope="col"
type="search" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
bind:value={searchvalue} {$_('name')}
placeholder={$_('datatable.search')} </th>
aria-label={$_('datatable.search')} <th
class="gridjs-input gridjs-search-input mb-4" /> scope="col"
<div class="block mb-6"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<label {$_('contact-information')}
for="country" </th>
class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label> <th
<Select scope="col"
on:select={(event) => { class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
selectedFilter = event.detail; {$_('group')}
}} </th>
selectedValue={selectedFilter} <th
placeholder={$_('filter-by-organization-team')} scope="col"
containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
items={selectgroups} {$_('distance-in-km')}
isMulti={true} /> </th>
</div> <th scope="col" class="relative px-6 py-3">
<div class="h-12"> <span class="sr-only">{$_('action')}</span>
{#if current_runners.some((r) => r.is_selected === true)} </th>
<div id="sponsoring:dropdown" class="relative inline-block"> </tr>
<div> </thead>
<button <tbody class="divide-y divide-gray-200">
on:click={() => { {#each current_runners as runner}
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; {#if runner.firstname
}} .toLowerCase()
type="button" .includes(
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" searchvalue.toLowerCase()
id="options-menu" ) || runner.lastname
aria-haspopup="true" .toLowerCase()
aria-expanded="true"> .includes(
{$_('generate-sponsoring-contracts')} searchvalue.toLowerCase()
<svg ) || should_display_based_on_id(runner.id)}
xmlns="http://www.w3.org/2000/svg" {#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0}
width="24" <tr
height="24" data-rowid="user_{runner.id}"
viewBox="0 0 24 24" data-groupid={runner.group.id}>
class="-mr-1 ml-2 h-5 w-5"><path <td class="px-6 py-4 whitespace-nowrap">
fill="none" <input
d="M0 0h24v24H0z" /> bind:checked={runner.is_selected}
<path type="checkbox"
fill="currentColor" class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg> </td>
</button> <td class="px-6 py-4 whitespace-nowrap">
</div> <div class="flex items-center">
{#if sponsoring_contracts_download_open} <div class="ml-4">
<div <div class="text-sm font-medium text-gray-900">
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" {runner.firstname}
id="sponsoring:dropdown:menu"> {runner.middlename || ''}
<div {runner.lastname}
class="py-1" </div>
role="menu" </div>
aria-orientation="vertical" </div>
aria-labelledby="options-menu"> </td>
<span <td class="px-6 py-4 whitespace-nowrap">
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> {#if runner.email}
<button <div class="text-sm text-gray-500">{runner.email}</div>
on:click={() => { {/if}
generateSponsoringContract('de'); {#if runner.phone}
}} <div class="text-sm text-gray-500">{runner.phone}</div>
type="submit" {/if}
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" {#if runner.address.address1 !== null}
role="menuitem"> {runner.address.address1}<br />
{$_('german')} {runner.address.address2 || ''}<br />
</button> {runner.address.postalcode}
<button {runner.address.city}
on:click={() => { {runner.address.country}
generateSponsoringContract('en'); {/if}
}} </td>
type="submit" <td class="px-6 py-4 whitespace-nowrap">
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" {#if runner.group.responseType === 'RUNNERTEAM'}
role="menuitem"> <a
{$_('english')} href="../teams/{runner.group.id}"
</button> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
</div> {/if}
</div> {#if runner.group.responseType === 'RUNNERORGANIZATION'}
{/if} <a
</div> href="../orgs/{runner.group.id}"
{/if} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
</div> {/if}
<div </td>
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> <td class="px-6 py-4 whitespace-nowrap">
<table class="divide-y divide-gray-200 w-full"> {runner.distance}
<thead class="bg-gray-50"> </td>
<tr> {#if active_deletes[runner.id] === true}
<th <td
scope="col" class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <button
<span on:click={() => {
on:click={() => { active_deletes[runner.id] = false;
const newstate = !current_runners.some((r) => r.is_selected === true); }}
current_runners = current_runners.map((r) => { tabindex="0"
r.is_selected = newstate; class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
return r; <button
}); on:click={() => {
}} RunnerService.runnerControllerRemove(runner.id, true)
class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)} .then((resp) => {
{$_('deselect-all')} current_runners = current_runners.filter((obj) => obj.id !== runner.id);
{:else}{$_('select-all')}{/if} })
</span> .catch((err) => {});
</th> }}
<th tabindex="0"
scope="col" class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </td>
{$_('name')} {:else}
</th> <td
<th class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
scope="col" <a
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> href="./{runner.id}"
{$_('contact-information')} class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
</th> {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
<th <button
scope="col" on:click={() => {
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> active_deletes[runner.id] = true;
{$_('group')} }}
</th> tabindex="0"
<th class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
scope="col" {/if}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </td>
{$_('distance-in-km')} {/if}
</th> </tr>
<th scope="col" class="relative px-6 py-3"> {/if}
<span class="sr-only">{$_('action')}</span> {/if}
</th> {/each}
</tr> </tbody>
</thead> </table>
<tbody class="divide-y divide-gray-200"> </div>
{#each current_runners as runner} {/if}
{#if runner.firstname {:catch error}
.toLowerCase() <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
.includes( <span class="inline-block align-middle mr-8">
searchvalue.toLowerCase() <b class="capitalize">{$_('general_promise_error')}</b>
) || runner.lastname {error}
.toLowerCase() </span>
.includes( </div>
searchvalue.toLowerCase() {/await}
) || should_display_based_on_id(runner.id)} {/if}
{#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0}
<tr
data-rowid="user_{runner.id}"
data-groupid={runner.group.id}>
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={runner.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{runner.firstname}
{runner.middlename || ''}
{runner.lastname}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if runner.email}
<div class="text-sm text-gray-500">{runner.email}</div>
{/if}
{#if runner.phone}
<div class="text-sm text-gray-500">{runner.phone}</div>
{/if}
{#if runner.address.address1 !== null}
{runner.address.address1}<br />
{runner.address.address2 || ''}<br />
{runner.address.postalcode}
{runner.address.city}
{runner.address.country}
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if runner.group.responseType === 'RUNNERTEAM'}
<a
href="../teams/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
{/if}
{#if runner.group.responseType === 'RUNNERORGANIZATION'}
<a
href="../orgs/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{runner.distance}
</td>
{#if active_deletes[runner.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[runner.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerService.runnerControllerRemove(runner.id, true)
.then((resp) => {
current_runners = current_runners.filter((obj) => obj.id !== runner.id);
})
.catch((err) => {});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{runner.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
<button
on:click={() => {
active_deletes[runner.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -23,14 +23,14 @@
throw new Error(); throw new Error();
} }
Toastify({ Toastify({
text: $_('copied-token-to-clipboard'), text: $_("copied-token-to-clipboard"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
copied = true; copied = true;
} catch (err) { } catch (err) {
Toastify({ Toastify({
text: $_('error-whyile-copying-to-clipboard'), text: $_("error-whyile-copying-to-clipboard"),
duration: 500, duration: 500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
@@ -75,7 +75,9 @@
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">{$_('token')}</h3> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('token')}
</h3>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
{$_('the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')} {$_('the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')}
@@ -106,7 +108,9 @@
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg> d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg>
</div> </div>
</div> </div>
<p class="text-gray-500 text-xs">{$_('click-to-copy-token-to-clipboard')}</p> <p class="text-gray-500 text-xs">
{$_('click-to-copy-token-to-clipboard')}
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,399 +1,297 @@
<script> <script>
import { import {
GroupContactService, GroupContactService,
RunnerOrganizationService, RunnerOrganizationService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import store from "../../store"; import store from "../../store";
import Select from "svelte-select"; import Select from "svelte-select";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte"; import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte"; import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
import Teams from "./Teams.svelte"; import Teams from "./Teams.svelte";
let [teamdata, original, delete_team, orgs, contacts, modal_open] = [ import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
{}, import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
{}, let [teamdata, original, delete_team, orgs, contacts, modal_open] = [
{}, {},
[], {},
[], {},
false, [],
]; [],
export let params; false,
export let import_modal_open = false; ];
$: delete_triggered = false; export let params;
$: save_enabled = !data_changed && teamdata.parentGroup != null; export let import_modal_open = false;
$: data_loaded = false; $: delete_triggered = false;
$: data_changed = JSON.stringify(teamdata) === JSON.stringify(original); $: save_enabled = !data_changed && teamdata.parentGroup != null;
$: sponsoring_contracts_download_open = false; $: data_loaded = false;
$: group = {}; $: data_changed = JSON.stringify(teamdata) === JSON.stringify(original);
$: contact = {}; $: sponsoring_contracts_show = true;
// $: cards_show = true;
const getContactLabel = (option) => $: generate_teams = [original];
option.firstname + " " + (option.middlename || "") + " " + option.lastname; $: group = {};
const promise = RunnerTeamService.runnerTeamControllerGetOne( $: contact = {};
params.teamid //
).then((value) => { const getContactLabel = (option) =>
data_loaded = true; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
teamdata = Object.assign(teamdata, value); const promise = RunnerTeamService.runnerTeamControllerGetOne(
original = Object.assign(original, value); params.teamid
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { ).then((value) => {
orgs = val.map((r) => { data_loaded = true;
return { label: r.name, value: r }; teamdata = Object.assign(teamdata, value);
}); original = Object.assign(original, value);
group = orgs.find((g) => g.value.id == teamdata.parentGroup.id); RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
}); (val) => {
GroupContactService.groupContactControllerGetAll().then((val) => { orgs = val.map((r) => {
contacts = val.map((r) => { return { label: r.name, value: r };
return { label: getContactLabel(r), value: r }; });
}); group = orgs.find((g) => g.value.id == teamdata.parentGroup.id);
if(teamdata.contact){ }
contact = contacts.find((g) => g.value.id == teamdata.contact.id); );
} GroupContactService.groupContactControllerGetAll().then((val) => {
else{ contacts = val.map((r) => {
contact = null; return { label: getContactLabel(r), value: r };
} });
}); if (teamdata.contact) {
}); contact = contacts.find((g) => g.value.id == teamdata.contact.id);
document.addEventListener("click", function (e) { } else {
if ( contact = null;
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && }
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" });
) { });
sponsoring_contracts_download_open = false; function deleteTeam() {
} RunnerTeamService.runnerTeamControllerRemove(original.id, false)
}); .then((resp) => {
function deleteTeam() { Toastify({
RunnerTeamService.runnerTeamControllerRemove(original.id, false) text: "Organization deleted",
.then((resp) => { duration: 500,
Toastify({ backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
text: "Organization deleted", }).showToast();
duration: 500, location.replace("./");
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", })
}).showToast(); .catch((err) => {
location.replace("./"); modal_open = true;
}) delete_team = original;
.catch((err) => { });
modal_open = true; }
delete_team = original; function submit() {
}); if (data_loaded === true && save_enabled) {
} Toastify({
function submit() { text: "updating team",
if (data_loaded === true && save_enabled) { duration: 2500,
Toastify({ }).showToast();
text: "updating team", let postdata = teamdata;
duration: 2500, postdata.parentGroup = teamdata.parentGroup.id;
}).showToast(); postdata.contact = teamdata.contact?.id;
let postdata = teamdata; RunnerTeamService.runnerTeamControllerPut(original.id, postdata)
postdata.parentGroup = teamdata.parentGroup.id; .then((resp) => {
postdata.contact = teamdata.contact?.id; Object.assign(original, teamdata);
RunnerTeamService.runnerTeamControllerPut(original.id, postdata) original = original;
.then((resp) => { Toastify({
Object.assign(original, teamdata); text: "updated team",
original = original; duration: 2500,
Toastify({ backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
text: "updated team", }).showToast();
duration: 2500, })
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", .catch((err) => {});
}).showToast(); }
}) }
.catch((err) => {}); </script>
}
} <ImportRunnerModal
async function generateSponsoringContract(locale) { current_runners={[]}
sponsoring_contracts_download_open = false; on:cancelDelete={(event) => {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( import_modal_open = false;
teamdata.id }}
); passed_team={teamdata}
const toast = Toastify({ passed_orgs={[]}
text: $_("generating-pdf"), passed_org={{}}
duration: -1, opened_from="TeamDetail"
}).showToast(); bind:import_modal_open />
fetch( <ConfirmTeamDeletion bind:modal_open bind:delete_team />
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, {#if data_loaded}
{ <section class="container p-5">
method: "POST", <div class="mb-8 text-3xl font-extrabold leading-tight">
headers: { {original.name}
"Content-Type": "application/json", <span data-id="org_actions_${teamdata.id}">
}, <GenerateSponsoringContracts
body: JSON.stringify(runners), bind:sponsoring_contracts_show
} bind:generate_teams />
) <GenerateRunnerCards
.then((response) => { bind:cards_show
if (response.status != "200") { bind:generate_teams />
toast.hideToast(); {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
Toastify({ <button
text: $_("pdf-generation-failed"), on:click={() => {
duration: 3500, import_modal_open = true;
backgroundColor: }}
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", type="button"
}).showToast(); 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:ml-3 sm:w-auto sm:text-sm">
} else { {$_('import-runners')}
return response.blob(); </button>
} {/if}
}) {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
.then((blob) => { {#if delete_triggered}
const url = window.URL.createObjectURL(blob); <button
let a = document.createElement("a"); on:click={deleteTeam}
a.href = url; class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button>
a.download = "Sponsorings_" + teamdata.name + ".pdf"; <button
document.body.appendChild(a); on:click={() => {
a.click(); delete_triggered = !delete_triggered;
a.remove(); }}
toast.hideToast(); class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
Toastify({ {/if}
text: $_("pdf-successfully-generated"), {#if !delete_triggered}
duration: 3500, <button
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", on:click={() => {
}).showToast(); delete_triggered = true;
}) }}
.catch((err) => {}); type="button"
} class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-team')}</button>
</script> {/if}
{/if}
<ImportRunnerModal {#if !delete_triggered}
current_runners={[]} <button
on:cancelDelete={(event) => { on:click={submit}
import_modal_open = false; disabled={!save_enabled}
}} class:opacity-50={!save_enabled}
passed_team={teamdata} type="button"
passed_orgs={[]} 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:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
passed_org={{}} {/if}
opened_from="TeamDetail" </span>
bind:import_modal_open /> </div>
<ConfirmTeamDeletion bind:modal_open bind:delete_team /> <div class="flex flex-row mb-4">
{#if data_loaded} <div class="w-full">
<section class="container p-5"> <nav class="w-full flex">
<div class="mb-8 text-3xl font-extrabold leading-tight"> <ol class="list-none flex flex-row items-center justify-start">
{original.name} <li class="mr-2 flex items-center">
<span data-id="org_actions_${teamdata.id}"> <svg
<div id="sponsoring:dropdown" class="relative inline-block"> stroke="currentColor"
<div> fill="none"
<button stroke-width="2"
on:click={() => { viewBox="0 0 24 24"
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; stroke-linecap="round"
}} stroke-linejoin="round"
type="button" class="h-3 w-3 stroke-current"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" height="1em"
id="options-menu" width="1em"
aria-haspopup="true" xmlns="http://www.w3.org/2000/svg"><path
aria-expanded="true"> d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
{$_('generate-sponsoring-contracts')} <polyline points="9 22 9 12 15 12 15 22" /></svg>
<svg </li>
xmlns="http://www.w3.org/2000/svg" <li class="flex items-center">
width="24" <a class="mr-2" href="/">Home</a><svg
height="24" stroke="currentColor"
viewBox="0 0 24 24" fill="none"
class="-mr-1 ml-2 h-5 w-5"><path stroke-width="2"
fill="none" viewBox="0 0 24 24"
d="M0 0h24v24H0z" /> stroke-linecap="round"
<path stroke-linejoin="round"
fill="currentColor" class="h-3 w-3 mr-2 stroke-current"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg> height="1em"
</button> width="1em"
</div> xmlns="http://www.w3.org/2000/svg"><line
{#if sponsoring_contracts_download_open} x1="5"
<div y1="12"
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" x2="19"
id="sponsoring:dropdown:menu"> y2="12" />
<div <polyline points="12 5 19 12 12 19" /></svg>
class="py-1" </li>
role="menu" <li class="mr-2 flex items-center">
aria-orientation="vertical" <svg
aria-labelledby="options-menu"> class="flex-shrink-0 w-5 h-5 mr-2"
<span fill="currentColor"
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> width="24"
<button height="24"
on:click={() => { xmlns="http://www.w3.org/2000/svg"
generateSponsoringContract('de'); viewBox="0 0 640 512"><path
}} fill="currentColor"
type="submit" 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>
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" </li>
role="menuitem"> <li class="flex items-center">
{$_('german')} <a class="mr-2" href="./">Teams</a><svg
</button> stroke="currentColor"
<button fill="none"
on:click={() => { stroke-width="2"
generateSponsoringContract('en'); viewBox="0 0 24 24"
}} stroke-linecap="round"
type="submit" stroke-linejoin="round"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" class="h-3 w-3 mr-2 stroke-current"
role="menuitem"> height="1em"
{$_('english')} width="1em"
</button> xmlns="http://www.w3.org/2000/svg"><line
</div> x1="5"
</div> y1="12"
{/if} x2="19"
</div> y2="12" />
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} <polyline points="12 5 19 12 12 19" /></svg>
<button </li>
on:click={() => { <li class="flex items-center">
import_modal_open = true; <span class="mr-2">Team-Details #{params.teamid}</span>
}} </li>
type="button" </ol>
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:ml-3 sm:w-auto sm:text-sm"> </nav>
{$_('import-runners')} </div>
</button> </div>
{/if} <div class="text-sm w-full">
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} <label for="name" class="font-medium text-gray-700">Name</label>
{#if delete_triggered} <input
<button autocomplete="off"
on:click={deleteTeam} placeholder="Name"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button> type="text"
<button bind:value={teamdata.name}
on:click={() => { name="name"
delete_triggered = !delete_triggered; class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
}} </div>
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> <div class="text-sm w-full">
{/if} <label
{#if !delete_triggered} for="contact"
<button class="font-medium text-gray-700">{$_('contact')}</label>
on:click={() => { <Select
delete_triggered = true; containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
}} itemFilter={(label, filterText, option) => label
type="button" .toLowerCase()
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-team')}</button> .includes(
{/if} filterText.toLowerCase()
{/if} ) || option.value.id
{#if !delete_triggered} .toString()
<button .startsWith(filterText.toLowerCase())}
on:click={submit} items={contacts}
disabled={!save_enabled} showChevron={true}
class:opacity-50={!save_enabled} placeholder={$_('no-contact-selected')}
type="button" noOptionsMessage={$_('no-contact-found')}
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:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> bind:selectedValue={contact}
{/if} on:select={(selectedValue) => (teamdata.contact = selectedValue.detail.value)}
</span> on:clear={() => (teamdata.contact = null)} />
</div> </div>
<div class="flex flex-row mb-4"> <div class="text-sm w-full">
<div class="w-full"> <label
<nav class="w-full flex"> for="org"
<ol class="list-none flex flex-row items-center justify-start"> class="font-medium text-gray-700">{$_('organization')}</label>
<li class="mr-2 flex items-center"> <Select
<svg containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
stroke="currentColor" itemFilter={(label, filterText, option) => label
fill="none" .toLowerCase()
stroke-width="2" .includes(
viewBox="0 0 24 24" filterText.toLowerCase()
stroke-linecap="round" ) || option.id.value
stroke-linejoin="round" .toString()
class="h-3 w-3 stroke-current" .startsWith(filterText.toLowerCase())}
height="1em" items={orgs}
width="1em" showChevron={true}
xmlns="http://www.w3.org/2000/svg"><path placeholder={$_('search-for-an-organization-by-name-or-id')}
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> noOptionsMessage={$_('no-organizations-found')}
<polyline points="9 22 9 12 15 12 15 22" /></svg> bind:selectedValue={group}
</li> on:select={(selectedValue) => (teamdata.parentGroup = selectedValue.detail.value)}
<li class="flex items-center"> on:clear={() => (teamdata.parentGroup = null)} />
<a class="mr-2" href="/">Home</a><svg </div>
stroke="currentColor" </section>
fill="none" {:else}
stroke-width="2" {#await promise}
viewBox="0 0 24 24" {$_('team-detail-is-being-loaded')}
stroke-linecap="round" {:catch error}
stroke-linejoin="round" <PromiseError />
class="h-3 w-3 mr-2 stroke-current" {/await}
height="1em" {/if}
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="mr-2 flex items-center">
<svg
class="flex-shrink-0 w-5 h-5 mr-2"
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>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">Teams</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">Team-Details #{params.teamid}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="text-sm w-full">
<label for="name" class="font-medium text-gray-700">Name</label>
<input
autocomplete="off"
placeholder="Name"
type="text"
bind:value={teamdata.name}
name="name"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="contact"
class="font-medium text-gray-700">{$_('contact')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label
.toLowerCase()
.includes(
filterText.toLowerCase()
) || option.value.id
.toString()
.startsWith(filterText.toLowerCase())}
items={contacts}
showChevron={true}
placeholder={$_('no-contact-selected')}
noOptionsMessage={$_('no-contact-found')}
bind:selectedValue={contact}
on:select={(selectedValue)=> teamdata.contact = selectedValue.detail.value}
on:clear={() => (teamdata.contact = null)} />
</div>
<div class="text-sm w-full">
<label
for="org"
class="font-medium text-gray-700">{$_('organization')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => label
.toLowerCase()
.includes(
filterText.toLowerCase()
) || option.id.value.toString().startsWith(filterText.toLowerCase())}
items={orgs}
showChevron={true}
placeholder={$_('search-for-an-organization-by-name-or-id')}
noOptionsMessage={$_('no-organizations-found')}
bind:selectedValue={group}
on:select={(selectedValue)=> teamdata.parentGroup = selectedValue.detail.value}
on:clear={() => (teamdata.parentGroup = null)} />
</div>
</section>
{:else}
{#await promise}
{$_('team-detail-is-being-loaded')}
{:catch error}
<PromiseError />
{/await}
{/if}

View File

@@ -1,330 +1,217 @@
<script> <script>
import { getLocaleFromNavigator, t, _ } from "svelte-i18n"; import { getLocaleFromNavigator, t, _ } from "svelte-i18n";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { RunnerTeamService } from "@odit/lfk-client-js"; import { RunnerTeamService } from "@odit/lfk-client-js";
const teams_promise = RunnerTeamService.runnerTeamControllerGetAll(); const teams_promise = RunnerTeamService.runnerTeamControllerGetAll();
import store, { users as usersstore } from "../../store.js"; import store, { users as usersstore } from "../../store.js";
import TeamsEmptyState from "./TeamsEmptyState.svelte"; import TeamsEmptyState from "./TeamsEmptyState.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte"; import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
$: searchvalue = ""; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
$: active_deletes = []; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: sponsoring_contracts_download_open = false; $: searchvalue = "";
export let current_teams = []; $: active_deletes = [];
let modal_open = false; $: sponsoring_contracts_show = current_teams.some(
let delete_team = {}; (r) => r.is_selected === true
usersstore.subscribe((val) => { );
current_teams = val; $: cards_show = current_teams.some(
}); (r) => r.is_selected === true
teams_promise.then((data) => { );
usersstore.set(data); $: generate_teams = current_teams.filter((r) => r.is_selected === true);
}); export let current_teams = [];
document.addEventListener("click", function (e) { let modal_open = false;
if ( let delete_team = {};
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && usersstore.subscribe((val) => {
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" current_teams = val;
) { });
sponsoring_contracts_download_open = false; teams_promise.then((data) => {
} usersstore.set(data);
}); });
async function generateSponsoringContract(locale) { </script>
sponsoring_contracts_download_open = false;
const teams = current_teams.filter((r) => r.is_selected === true); <ConfirmTeamDeletion
const toast = Toastify({ on:cancelDelete={(event) => {
text: $_("generating-pdfs"), modal_open = false;
duration: -1, active_deletes[event.detail.id] = false;
}).showToast(); }}
let count = 0; bind:modal_open
for await (const t of teams) { bind:delete_team />
count++; {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( {#await teams_promise}
t.id <div
); class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
fetch( role="alert">
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, <p class="font-bold">{$_('teams-are-being-loaded')}</p>
{ <p class="text-sm">{$_('this-might-take-a-moment')}</p>
method: "POST", </div>
headers: { {:then}
"Content-Type": "application/json", {#if current_teams.length === 0}
}, <TeamsEmptyState />
body: JSON.stringify(runners), {:else}
} <input
) type="search"
.then((response) => { bind:value={searchvalue}
if (response.status != "200") { placeholder={$_('datatable.search')}
toast.hideToast(); aria-label={$_('datatable.search')}
Toastify({ class="gridjs-input gridjs-search-input mb-4" />
text: $_("pdf-generation-failed"), <div class="h-12">
duration: 3500, <GenerateSponsoringContracts
backgroundColor: bind:sponsoring_contracts_show
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", bind:generate_teams />
}).showToast(); <GenerateRunnerCards
} else { bind:cards_show
return response.blob(); bind:generate_teams />
} </div>
}) <div
.then((blob) => { class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
const url = window.URL.createObjectURL(blob); <table class="divide-y divide-gray-200 w-full">
let a = document.createElement("a"); <thead class="bg-gray-50">
a.href = url; <tr>
a.download = "Sponsorings_" + t.name + ".pdf"; <th
document.body.appendChild(a); scope="col"
a.click(); class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
a.remove(); <span
if (count === teams.length) { on:click={() => {
toast.hideToast(); const newstate = !current_teams.some((r) => r.is_selected === true);
Toastify({ current_teams = current_teams.map((r) => {
text: $_("pdfs-successfully-generated"), r.is_selected = newstate;
duration: 3500, return r;
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", });
}).showToast(); }}
} class="underline cursor-pointer select-none">{#if current_teams.some((r) => r.is_selected === true)}
}) {$_('deselect-all')}
.catch((err) => {}); {:else}{$_('select-all')}{/if}
} </span>
} </th>
</script> <th
scope="col"
<ConfirmTeamDeletion class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
on:cancelDelete={(event) => { {$_('name')}
modal_open = false; </th>
active_deletes[event.detail.id] = false; <th
}} scope="col"
bind:modal_open class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
bind:delete_team /> {$_('organization')}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} </th>
{#await teams_promise} <th
<div scope="col"
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
role="alert"> {$_('contact')}
<p class="font-bold">{$_('teams-are-being-loaded')}</p> </th>
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <th scope="col" class="relative px-6 py-3">
</div> <span class="sr-only">{$_('action')}</span>
{:then} </th>
{#if current_teams.length === 0} </tr>
<TeamsEmptyState /> </thead>
{:else} <tbody class="divide-y divide-gray-200">
<input {#each current_teams as t}
type="search" {#if Object.values(t)
bind:value={searchvalue} .toString()
placeholder={$_('datatable.search')} .toLowerCase()
aria-label={$_('datatable.search')} .includes(searchvalue)}
class="gridjs-input gridjs-search-input mb-4" /> <tr data-rowid="team_{t.id}">
<div class="h-12"> <td class="px-6 py-4 whitespace-nowrap">
{#if current_teams.some((r) => r.is_selected === true)} <input
<div id="sponsoring:dropdown" class="relative inline-block"> bind:checked={t.is_selected}
<div> type="checkbox"
<button class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
on:click={() => { </td>
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; <td class="px-6 py-4 whitespace-nowrap">
}} <div class="flex items-center">
type="button" <div class="ml-4">
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex" <div class="text-sm font-medium text-gray-900">
id="options-menu" {t.name}
aria-haspopup="true" </div>
aria-expanded="true"> </div>
{$_('generate-sponsoring-contracts')} </div>
<svg </td>
xmlns="http://www.w3.org/2000/svg" <td class="px-6 py-4 whitespace-nowrap">
width="24" <div class="flex items-center">
height="24" <div class="ml-4">
viewBox="0 0 24 24" <div class="text-sm font-medium text-gray-900">
class="-mr-1 ml-2 h-5 w-5"><path {#if t.parentGroup}
fill="none" <a
d="M0 0h24v24H0z" /> href="../orgs/{t.parentGroup.id}"
<path class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a>
fill="currentColor" {:else}{$_('no-organization-specified')}{/if}
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg> </div>
</button> </div>
</div> </div>
{#if sponsoring_contracts_download_open} </td>
<div <td class="px-6 py-4 whitespace-nowrap">
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5" <div class="flex items-center">
id="sponsoring:dropdown:menu" <div class="ml-4">
on:click_outside={() => { <div class="text-sm font-medium text-gray-900">
sponsoring_contracts_download_open = false; {#if t.contact}
}}> <a
<div href="../contacts/{t.contact.id}"
class="py-1" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname}
role="menu" {t.contact.middlename || ''}
aria-orientation="vertical" {t.contact.lastname}</a>
aria-labelledby="options-menu"> {:else}{$_('no-contact-specified')}{/if}
<span </div>
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> </div>
<button </div>
on:click={() => { </td>
generateSponsoringContract('de'); {#if active_deletes[t.id] === true}
}} <td
type="submit" class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" <button
role="menuitem"> on:click={() => {
{$_('german')} active_deletes[t.id] = false;
</button> }}
<button tabindex="0"
on:click={() => { class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel
generateSponsoringContract('en'); Delete</button>
}} <button
type="submit" on:click={() => {
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex" RunnerTeamService.runnerTeamControllerRemove(t.id, false)
role="menuitem"> .then((resp) => {
{$_('english')} current_teams = current_teams.filter((obj) => obj.id !== t.id);
</button> Toastify({
</div> text: $_('organization-deleted'),
</div> duration: 500,
{/if} backgroundColor:
</div> 'linear-gradient(to right, #00b09b, #96c93d)',
{/if} }).showToast();
</div> })
<div .catch((err) => {
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> modal_open = true;
<table class="divide-y divide-gray-200 w-full"> delete_team = t;
<thead class="bg-gray-50"> });
<tr> }}
<th tabindex="0"
scope="col" class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </td>
<span {:else}
on:click={() => { <td
const newstate = !current_teams.some((r) => r.is_selected === true); class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
current_teams = current_teams.map((r) => { <a
r.is_selected = newstate; href="./{t.id}"
return r; class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
}); {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
}} <button
class="underline cursor-pointer select-none">{#if current_teams.some((r) => r.is_selected === true)} on:click={() => {
{$_('deselect-all')} active_deletes[t.id] = true;
{:else}{$_('select-all')}{/if} }}
</span> tabindex="0"
</th> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
<th {/if}
scope="col" </td>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> {/if}
{$_('name')} </tr>
</th> {/if}
<th {/each}
scope="col" </tbody>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </table>
{$_('organization')} </div>
</th> {/if}
<th {:catch error}
scope="col" <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <span class="inline-block align-middle mr-8">
{$_('contact')} <b class="capitalize">{$_('general_promise_error')}</b>
</th> {error}
<th scope="col" class="relative px-6 py-3"> </span>
<span class="sr-only">{$_('action')}</span> </div>
</th> {/await}
</tr> {/if}
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_teams as t}
{#if Object.values(t)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="team_{t.id}">
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={t.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{t.name}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if t.parentGroup}
<a
href="../orgs/{t.parentGroup.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a>
{:else}{$_('no-organization-specified')}{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if t.contact}
<a
href="../contacts/{t.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname}
{t.contact.middlename || ''}
{t.contact.lastname}</a>
{:else}{$_('no-contact-specified')}{/if}
</div>
</div>
</div>
</td>
{#if active_deletes[t.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[t.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel
Delete</button>
<button
on:click={() => {
RunnerTeamService.runnerTeamControllerRemove(t.id, false)
.then((resp) => {
current_teams = current_teams.filter((obj) => obj.id !== t.id);
Toastify({
text: $_('organization-deleted'),
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_team = t;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{t.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
<button
on:click={() => {
active_deletes[t.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -1,392 +1,420 @@
{ {
"404message": "Die gesuchte Seite wurde leider nicht gefunden.", "404message": "Die gesuchte Seite wurde leider nicht gefunden.",
"404title": "Fehler 404", "404title": "Fehler 404",
"about": "Über", "about": "Über",
"action": "Aktionen", "action": "Aktionen",
"active": "Aktiv", "active": "Aktiv",
"add-donation": "Sponsoring erstellen", "add-card": "Karte erstellen",
"add-donor": "Sponsor:in erstellen", "add-donation": "Sponsoring erstellen",
"add-scan": "Scan erstellen", "add-donor": "Sponsor:in erstellen",
"add-the-first-scanstation": "Erstelle deine erste Scannerstation.", "add-scan": "Scan erstellen",
"add-user-group": "Neue Gruppe erstellen", "add-the-first-scanstation": "Erstelle deine erste Scannerstation.",
"add-your-first-contact": "Erstelle den ersten Kontakt", "add-user-group": "Neue Gruppe erstellen",
"add-your-first-donor": "Erstelle die erste Sponsor:in", "add-your-first-card": "Erstelle deine erste Läuferkarte",
"add-your-first-group": "Erstelle die erste Gruppe", "add-your-first-contact": "Erstelle den ersten Kontakt",
"add-your-first-organization": "Erstelle die erste Organisation", "add-your-first-donor": "Erstelle die erste Sponsor:in",
"add-your-first-runner": "Erstelle die erste Läufer:in", "add-your-first-group": "Erstelle die erste Gruppe",
"add-your-first-team": "Erstelle das erste Team", "add-your-first-organization": "Erstelle die erste Organisation",
"add-your-first-track": "Erstelle den ersten Track (Laufstrecke).", "add-your-first-runner": "Erstelle die erste Läufer:in",
"add-your-first-user": "Erstelle die erste Benutzer:in", "add-your-first-team": "Erstelle das erste Team",
"add-your-fist-donation": "Erstelle dein erstes Sponsoring", "add-your-first-track": "Erstelle den ersten Track (Laufstrecke).",
"add-your-fist-scan": "Füge deinen ersten Scan hinzu", "add-your-first-user": "Erstelle die erste Benutzer:in",
"adding-scan": "Scan wird hinzugefügt", "add-your-fist-donation": "Erstelle dein erstes Sponsoring",
"address": "Adresse", "add-your-fist-scan": "Füge deinen ersten Scan hinzu",
"address-is-required": "Du musst eine Adresse angeben", "adding-card": "Karte wird erstellt",
"after-deletion-we-cant-restore-your-old-profile": "Nach der Löschung können auch die Admins dein Profil nicht wiederherstellen!", "adding-scan": "Scan wird hinzugefügt",
"after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "Nach der Änderung wirst du abgemeldet - bitte melde dich dann mit deinem neuen Passwort an.", "address": "Adresse",
"all-associated-donations-will-get-deleted-as-well": "Alle Sponsorings dieser Sponsor:in werden ebenfalls gelöscht", "address-is-required": "Du musst eine Adresse angeben",
"all-associated-runners-will-be-deleted-too": "Alle zugehörigen Läufer:innen werden auch gelöscht!", "after-deletion-we-cant-restore-your-old-profile": "Nach der Löschung können auch die Admins dein Profil nicht wiederherstellen!",
"all-associated-teams-and-runners-will-be-deleted-too": "Alle assoziierten Teams und Läufer:innen werden auch gelöscht!", "after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "Nach der Änderung wirst du abgemeldet - bitte melde dich dann mit deinem neuen Passwort an.",
"amount-per-kilometer": "Betrag pro Kilometer", "all-associated-donations-will-get-deleted-as-well": "Alle Sponsorings dieser Sponsor:in werden ebenfalls gelöscht",
"apartment-suite-etc": "Apartment, Wohnung, etc.", "all-associated-runners-will-be-deleted-too": "Alle zugehörigen Läufer:innen werden auch gelöscht!",
"application_name": "Lauf für Kaya! - Admin", "all-associated-teams-and-runners-will-be-deleted-too": "Alle assoziierten Teams und Läufer:innen werden auch gelöscht!",
"applying-changes": "Änderungen anwenden", "amount": "Anzahl",
"attention": "Achtung!", "amount-per-kilometer": "Betrag pro Kilometer",
"author": "Autor:in", "apartment-suite-etc": "Apartment, Wohnung, etc.",
"bitte-bestaetige-diese-laeufer-fuer-den-import": "Bitte die Läufer:innen für den Import bestätigen.", "application_name": "Lauf für Kaya! - Admin",
"by": "von", "applying-changes": "Änderungen anwenden",
"cancel": "Abbrechen", "attention": "Achtung!",
"cancel-delete": "Löschen abbrechen", "author": "Autor:in",
"cancel-keep-donor": "Abbrechen, Sponsor:in behalten", "bitte-bestaetige-diese-laeufer-fuer-den-import": "Bitte die Läufer:innen für den Import bestätigen.",
"cancel-keep-my-profile": "Abbrechen, mein Profil behalten", "by": "von",
"cancel-keep-organization": "Abbrechen und Organisation bearbeiten", "cancel": "Abbrechen",
"cancel-keep-team": "Abbrechen, Team behalten", "cancel-delete": "Löschen abbrechen",
"cannot-reset-your-password-directly": "Schade. \nWir können das Passwort leider nicht direkt zurücksetzen.\nBitte sende uns eine Mail in der du deine Identität bestätigst.", "cancel-keep-donor": "Abbrechen, Sponsor:in behalten",
"change-your-password-here": "Hier kannst du dein Passwort ändern", "cancel-keep-my-profile": "Abbrechen, mein Profil behalten",
"changing-your-password": "Passwort wird geändert", "cancel-keep-organization": "Abbrechen und Organisation bearbeiten",
"city": "Stadt", "cancel-keep-team": "Abbrechen, Team behalten",
"click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren", "cannot-reset-your-password-directly": "Schade. \nWir können das Passwort leider nicht direkt zurücksetzen.\nBitte sende uns eine Mail in der du deine Identität bestätigst.",
"close": "Schließen", "card-added": "Karte wurde hinzugefügt",
"configure-the-tracks-and-minimum-lap-times": "Bearbeite die Tracks und ihre minimale Rundenzeit", "card-deleted": "Karte gelöscht",
"confirm": "Bestätigen", "card-updated": "Karte aktualisiert",
"confirm-delete": "Löschung Bestätigen", "cards": "Läuferkarten",
"confirm-delete-donor-with-all-donations": "Bestätigen, Sponsor:in mit allen Sponsorings löschen", "change-your-password-here": "Hier kannst du dein Passwort ändern",
"confirm-delete-my-user-profile": "Bestätigung, mein Benutzerprofil löschen", "changing-your-password": "Passwort wird geändert",
"confirm-delete-organization-and-associated-teams-runners": "Bestätugung, lösche die Organisation und alle zugehörigen Teams und Läufer:innen.", "city": "Stadt",
"confirm-delete-team-and-associated-runners": "Bestätigung, lösche das Team mitsamt seinen Läufer:innen.", "click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren",
"confirm-deletion": "Löschung Bestätigen", "close": "Schließen",
"confirm-the-new-password": "Neues Passwort bestätigen", "code": "Code",
"contact": "Kontakt", "configure-the-tracks-and-minimum-lap-times": "Bearbeite die Tracks und ihre minimale Rundenzeit",
"contact-deleted": "Kontakt gelöscht", "confirm": "Bestätigen",
"contact-information": "Kontaktinformation", "confirm-delete": "Löschung Bestätigen",
"contact-is-being-updated": "Kontakt wird aktualisiert ...", "confirm-delete-donor-with-all-donations": "Bestätigen, Sponsor:in mit allen Sponsorings löschen",
"contact-is-not-a-member-in-any-group": "Kontakt gehört zu keiner Gruppe", "confirm-delete-my-user-profile": "Bestätigung, mein Benutzerprofil löschen",
"contacts": "Kontakte", "confirm-delete-organization-and-associated-teams-runners": "Bestätugung, lösche die Organisation und alle zugehörigen Teams und Läufer:innen.",
"contacts-are-being-loaded": "Kontakte werden geladen ...", "confirm-delete-team-and-associated-runners": "Bestätigung, lösche das Team mitsamt seinen Läufer:innen.",
"copied-token-to-clipboard": "Token wurde in die Zwischenablage kopiert", "confirm-deletion": "Löschung Bestätigen",
"count_organizations": "Organisationen (Anzahl)", "confirm-the-new-password": "Neues Passwort bestätigen",
"count_teams": "Teams (Anzahl)", "contact": "Kontakt",
"create": "Erstellen", "contact-deleted": "Kontakt gelöscht",
"create-a-new": "Erstelle eine neue", "contact-information": "Kontaktinformation",
"create-a-new-contact": "Kontakt erstellen", "contact-is-being-updated": "Kontakt wird aktualisiert ...",
"create-a-new-distance-donation": "Erstelle ein neues Sponsoring", "contact-is-not-a-member-in-any-group": "Kontakt gehört zu keiner Gruppe",
"create-a-new-donor": "Neue Sponsor:in erstellen", "contacts": "Kontakte",
"create-a-new-fixed-donation": "Erstelle eine neue Festbetragsspende", "contacts-are-being-loaded": "Kontakte werden geladen ...",
"create-a-new-organization": "Neue Organisation anlegen", "copied-token-to-clipboard": "Token wurde in die Zwischenablage kopiert",
"create-a-new-runner": "Neue Läufer:in erstellen", "count_organizations": "Organisationen (Anzahl)",
"create-a-new-scan-fixed-only": "Neuen Scan erstellen (nur mit Festdistanz)", "count_teams": "Teams (Anzahl)",
"create-a-new-scanstation": "Neue Station erstellen", "create": "Erstellen",
"create-a-new-team": "Erstelle ein neues Team", "create-a-new": "Erstelle eine neue",
"create-a-new-track": "Neuen Track erstellen", "create-a-new-card": "Neue Läuferkarte erstellen",
"create-a-new-user": "Neue Benutzer:in anlegen", "create-a-new-contact": "Kontakt erstellen",
"create-a-new-user-group": "Erstelle eine neue Gruppe", "create-a-new-distance-donation": "Erstelle ein neues Sponsoring",
"create-organization": "Organisation erstellen", "create-a-new-donor": "Neue Sponsor:in erstellen",
"create-team": "Team erstellen", "create-a-new-fixed-donation": "Erstelle eine neue Festbetragsspende",
"create-track": "Track erstellen", "create-a-new-organization": "Neue Organisation anlegen",
"create-user": "Benutzer anlegen", "create-a-new-runner": "Neue Läufer:in erstellen",
"credits": "Credits", "create-a-new-scan-fixed-only": "Neuen Scan erstellen (nur mit Festdistanz)",
"csv_import__class": "Klasse", "create-a-new-scanstation": "Neue Station erstellen",
"csv_import__firstname": "Vorname", "create-a-new-team": "Erstelle ein neues Team",
"csv_import__lastname": "Nachname", "create-a-new-track": "Neuen Track erstellen",
"csv_import__middlename": "Mittelname", "create-a-new-user": "Neue Benutzer:in anlegen",
"csv_import__team": "Team", "create-a-new-user-group": "Erstelle eine neue Gruppe",
"danger-zone": "Gefahrenzone", "create-bulk-blanco-cards": "Blankokarten erstellen",
"dashboard-greeting": "Hallo", "create-bulk-cards": "Blankokarten erstellen",
"dashboard-title": "Dashboard", "create-organization": "Organisation erstellen",
"datatable": { "create-team": "Team erstellen",
"search": "🔍 Suche ...", "create-track": "Track erstellen",
"an_error_happened_while_fetching_the_data": "Beim Abrufen der Daten ist ein Fehler aufgetreten", "create-user": "Benutzer anlegen",
"loading": "Wird geladen...", "created-blanco-cards": "Blankokarten wurden erstellt",
"next": "Nächste", "creating-blanco-cards": "Erstelle Blankokarten",
"of": "von", "credits": "Credits",
"previous": "Vorherige", "csv_import__class": "Klasse",
"to": "bis", "csv_import__firstname": "Vorname",
"showing": "Zeige", "csv_import__lastname": "Nachname",
"no_matching_records_found": "Keine passenden Einträge gefunden", "csv_import__middlename": "Mittelname",
"page": "Seite", "csv_import__team": "Team",
"records": "Einträge", "danger-zone": "Gefahrenzone",
"sort_column_ascending": "Spalte aufsteigend sortieren", "dashboard-greeting": "Hallo",
"sort_column_descending": "Spalte absteigend sortieren" "dashboard-title": "Dashboard",
}, "datatable": {
"delete": "Löschen", "search": "🔍 Suche ...",
"delete-contact": "Kontakt löschen", "an_error_happened_while_fetching_the_data": "Beim Abrufen der Daten ist ein Fehler aufgetreten",
"delete-donation": "Sponsporing löschen", "loading": "Wird geladen...",
"delete-donor": "Sponsor:in löschen", "next": "Nächste",
"delete-group": "Gruppe löschen", "of": "von",
"delete-organization": "Organisation löschen", "previous": "Vorherige",
"delete-profile": "Profil löschen", "to": "bis",
"delete-runner": "Läufer:in löschen", "showing": "Zeige",
"delete-scan": "Scan löschen", "no_matching_records_found": "Keine passenden Einträge gefunden",
"delete-station": "Station löschen", "page": "Seite",
"delete-team": "Team Löschen", "records": "Einträge",
"delete-user": "Benutzer:in löschen", "sort_column_ascending": "Spalte aufsteigend sortieren",
"deleted-scan": "Scan wurde gelöscht", "sort_column_descending": "Spalte absteigend sortieren"
"dependency_name": "Name", },
"description": "Beschreibung", "delete": "schen",
"description-optional": "Beschreibung (optional)", "delete-contact": "Kontakt löschen",
"deselect-all": "Alle abwählen", "delete-donation": "Sponsporing löschen",
"details": "Details", "delete-donor": "Sponsor:in löschen",
"disabled": "deaktiviert", "delete-group": "Gruppe löschen",
"distance": "Distanz", "delete-organization": "Organisation löschen",
"distance-donation": "Sponsoring", "delete-profile": "Profil löschen",
"distance-in-km": "Distanz (in KM)", "delete-runner": "Läufer:in löschen",
"distance-track": "Distanz (+Track)", "delete-scan": "Scan löschen",
"do-you-really-want-to-delete-your-profile": "Möchtest du dein Profil wirklich löschen?", "delete-station": "Station löschen",
"do-you-want-to-delete-the-organization-delete_org-name": "Möchtest du die Organisation {orgname} löschen?", "delete-team": "Team Löschen",
"do-you-want-to-delete-the-team-delete_team-name": "Möchtest du das Team {teamname} löschen?", "delete-user": "Benutzer:in löschen",
"do-you-want-to-delete-this-donor-with-all-related-donations": "Möchtest du diese Sponsor:in mit all ihren Sponsorings löschen?", "deleted-scan": "Scan wurde gelöscht",
"documentation": "Dokumentation", "dependency_name": "Name",
"donation-amount": "Sponsoringbetrag", "description": "Beschreibung",
"donation-amount-must-be-greater-that-0-00eur": "Der Sponsoringbetrag muss größer als 0.00€ sein.", "description-optional": "Beschreibung (optional)",
"donations": "Sponsorings", "deselect-all": "Alle abwählen",
"donor": "Sponsor:in", "details": "Details",
"donor-added": "Sponsor:in hinzugefügt", "disabled": "deaktiviert",
"donor-deleted": "Sponsor:in gelöscht", "distance": "Distanz",
"donor-has-no-associated-donations": "Zur Sponsor:in gibt es noch keine Sponsorings", "distance-donation": "Sponsoring",
"donor-is-being-added": "Sponsor:in wird hinzugefügt...", "distance-in-km": "Distanz (in KM)",
"donor-is-being-updated": "Sponsor:in wird aktualisiert", "distance-track": "Distanz (+Track)",
"donors": "Sponsor:innen", "do-you-really-want-to-delete-your-profile": "Möchtest du dein Profil wirklich löschen?",
"donors-are-being-loaded": "Sponsor:innen werden geladen", "do-you-want-to-delete-the-organization-delete_org-name": "Möchtest du die Organisation {orgname} löschen?",
"dont-have-your-email-connected": "Deine E-Mail ist nicht verknüpft?", "do-you-want-to-delete-the-team-delete_team-name": "Möchtest du das Team {teamname} löschen?",
"dont-panic-were-resetting-it": "Keine Panik, wir setzen es zurück ✌", "do-you-want-to-delete-this-donor-with-all-related-donations": "Möchtest du diese Sponsor:in mit all ihren Sponsorings löschen?",
"e-mail-adress": "E-Mail-Adresse", "documentation": "Dokumentation",
"edit": "Bearbeiten", "donation-amount": "Sponsoringbetrag",
"edit-permissions": "Berechtigungen bearbeiten", "donation-amount-must-be-greater-that-0-00eur": "Der Sponsoringbetrag muss größer als 0.00€ sein.",
"email_address_or_username": "E-Mail-Adresse/ Benutzername", "donations": "Sponsorings",
"enabled": "aktiviert", "donor": "Sponsor:in",
"enabled_large": "Disabled", "donor-added": "Sponsor:in hinzugefügt",
"english": "Englisch", "donor-deleted": "Sponsor:in gelöscht",
"error-during-import": "Fehler beim Importieren", "donor-has-no-associated-donations": "Zur Sponsor:in gibt es noch keine Sponsorings",
"error-whyile-copying-to-clipboard": "Fehler beim Kopieren in die Zwischenablage", "donor-is-being-added": "Sponsor:in wird hinzugefügt...",
"error_on_login": "😢Fehler beim Login", "donor-is-being-updated": "Sponsor:in wird aktualisiert",
"erteilte": "Direkt erteilte", "donors": "Sponsor:innen",
"everything-concerning-your-profile": "Alles zu deinem Profil", "donors-are-being-loaded": "Sponsor:innen werden geladen",
"everything-is-more-fun-together": "Im Team macht's mehr Spaß 🏃‍♂️🏃‍♀️🏃‍♂️", "dont-have-your-email-connected": "Deine E-Mail ist nicht verknüpft?",
"faq": "FAQ", "dont-panic-were-resetting-it": "Keine Panik, wir setzen es zurück ✌",
"filter-by-organization-team": "Filtern nach Organisation / Team", "e-mail-adress": "E-Mail-Adresse",
"first-name": "Vorname", "edit": "Bearbeiten",
"first-name-is-required": "Vorname muss angegeben werden", "edit-a-card": "Läuferkarte bearbeiten",
"first-scan-of-the-day": "Erster Scan des Tages", "edit-permissions": "Berechtigungen bearbeiten",
"fixed-donation": "Festbetragsspende", "email_address_or_username": "E-Mail-Adresse/ Benutzername",
"forgot_password": "Passwort vergessen?", "enabled": "aktiviert",
"geerbte": "geerbte", "enabled_large": "Aktiviert",
"general-stats": "Allgemeine Statistiken", "english": "Englisch",
"general_promise_error": "😢 Ein unbekannter Fehler ist aufgetreten", "error-during-import": "Fehler beim Importieren",
"generate-sponsoring-contract": "Sponsoringvertrag generieren", "error-whyile-copying-to-clipboard": "Fehler beim Kopieren in die Zwischenablage",
"generate-sponsoring-contracts": "Sponsoringverträge generieren", "error_on_login": "😢Fehler beim Login",
"generating-pdf": "Pdf wird generiert...", "erteilte": "Direkt erteilte",
"generating-pdfs": "PDFs werden generiert...", "everything-concerning-your-profile": "Alles zu deinem Profil",
"generic-ui-logic-error": "Etwas ist in der Benutzeroberfläche schiefgelaufen.", "everything-is-more-fun-together": "Im Team macht's mehr Spaß 🏃‍♂️🏃‍♀️🏃‍♂️",
"german": "Deutsch", "faq": "FAQ",
"go-to-login": "Zum Login", "filter-by-organization-team": "Filtern nach Organisation / Team",
"goback": "Zur Startseite", "first-name": "Vorname",
"granted": "Gewährt", "first-name-is-required": "Vorname muss angegeben werden",
"group": "Gruppe", "first-scan-of-the-day": "Erster Scan des Tages",
"group-added": "Gruppe hinzugefügt", "fixed-donation": "Festbetragsspende",
"group-is-being-added": "Gruppe wird erstellt", "forgot_password": "Passwort vergessen?",
"group-name-is-required": "Der Gruppenname muss angegeben werden.", "geerbte": "geerbte",
"group-updated": "Gruppe aktualisiert", "general-stats": "Allgemeine Statistiken",
"groups": "Gruppen", "general_promise_error": "😢 Ein unbekannter Fehler ist aufgetreten",
"groups-are-being-loaded": "Gruppen werden geladen", "generate-runnercards": "Läuferkarten generieren",
"home": "Start", "generate-sponsoring-contract": "Sponsoringvertrag generieren",
"icon-image-credits": "Wir möchten uns außerdem für die verwendeten Icons und Bilder bedanken bei:", "generate-sponsoring-contracts": "Sponsoringverträge generieren",
"import-finished": "Import abgeschlossen", "generating-pdf": "Pdf wird generiert...",
"import-runners": "Läufer:innen importieren", "generating-pdfs": "PDFs werden generiert...",
"import__target-organization": "Ziel Organisation", "generic-ui-logic-error": "Etwas ist in der Benutzeroberfläche schiefgelaufen.",
"imprint": "Impressum ", "german": "Deutsch",
"imprint-loading": "Impressum lädt...", "go-to-login": "Zum Login",
"inactive": "Inaktiv", "goback": "Zur Startseite",
"installed-version": "Installierte Version", "granted": "Gewährt",
"internal-error": "Interner Fehler", "group": "Gruppe",
"invalid": "Ungültig", "group-added": "Gruppe hinzugefügt",
"invalid-mail-reset": "Das ist keine gültige E-Mail", "group-is-being-added": "Gruppe wird erstellt",
"laeufer-hinzufuegen": "Läufer:in hinzufügen", "group-name-is-required": "Der Gruppenname muss angegeben werden.",
"laeufer-importieren": "Läufer:innen importieren", "group-updated": "Gruppe aktualisiert",
"laptime": "Rundenzeit", "groups": "Gruppen",
"last-name": "Nachname", "groups-are-being-loaded": "Gruppen werden geladen",
"last-name-is-required": "Nachname muss angegeben werden", "home": "Start",
"lfk-is-os": "Das \"Lauf für Kaya!\" Frontend ist (wie alle anderen Projekte für den \"LfK!\" auch) ein OpenSource Projekt.", "icon-image-credits": "Wir möchten uns außerdem für die verwendeten Icons und Bilder bedanken bei:",
"license": "Lizenz", "if-you-want-to-create-multiple-blanco-cards-try-the-add-bulk-button": "Wenn du mehrere Blankokarten erstellen willst, nutze doch den \"Blankokarten erstellen\" Knopf.",
"licenses-are-being-loaded": "Lizenzen werden geladen...", "import-finished": "Import abgeschlossen",
"loading-contact-details": "Kontaktdaten werden geladen ...", "import-runners": "Läufer:innen importieren",
"loading-donation-details": "Lade Sponsoringdetails", "import__target-organization": "Ziel Organisation",
"loading-donor-details": "Lade Details", "imprint": "Impressum ",
"loading-group-detail": "Lade Gruppendetails...", "imprint-loading": "Impressum lädt...",
"loading-profile-data": "Lade Profildaten", "inactive": "Inaktiv",
"loading-runners": "Läufer:innen werden geladen...", "installed-version": "Installierte Version",
"loading-station-details": "Lade Scanstation-Details ...", "internal-error": "Interner Fehler",
"log_in": "Anmelden", "invalid": "Ungültig",
"log_in_to_your_account": "Bitte melde dich an", "invalid-mail-reset": "Das ist keine gültige E-Mail",
"login_is_checked": "Login wird überprüft", "just-enter-how-many-you-want-and-the-system-will-create-them": "Gebe einfach ein, wie viele Blankokarten das System erstellen soll.",
"logout": "Abmelden", "laeufer-hinzufuegen": "Läufer:in hinzufügen",
"mail-validation-in-progress": "E-Mail Verifizierung läuft... ", "laeufer-importieren": "Läufer:innen importieren",
"manage-admin-users": "Nutzer verwalten", "laptime": "Rundenzeit",
"middle-name": "Mittelname", "last-name": "Nachname",
"minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)", "last-name-is-required": "Nachname muss angegeben werden",
"minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein", "lfk-is-os": "Das \"Lauf für Kaya!\" Frontend ist (wie alle anderen Projekte für den \"LfK!\" auch) ein OpenSource Projekt.",
"name": "Name", "license": "Lizenz",
"name-is-required": "Der Gruppenname muss angegeben werden", "licenses-are-being-loaded": "Lizenzen werden geladen...",
"new-password": "Neues Passwort", "loading-cards": "Läuferkarten werden geladen",
"no-contact-found": "Keine Kontakte gefunden", "loading-contact-details": "Kontaktdaten werden geladen ...",
"no-contact-selected": "Kein Kontakt ausgewählt", "loading-donation-details": "Lade Sponsoringdetails",
"no-contact-specified": "Kein Kontakt angegeben", "loading-donor-details": "Lade Details",
"no-donors-found": "Keine Spender:innen gefunden", "loading-group-detail": "Lade Gruppendetails...",
"no-license-text-could-be-found": "Kein Lizenz-Text gefunden 😢", "loading-profile-data": "Lade Profildaten",
"no-organization-or-team-found": "Keine Organisationen oder Teams gefunden", "loading-runners": "Läufer:innen werden geladen...",
"no-organization-specified": "Keine Organisation angegeben", "loading-station-details": "Lade Scanstation-Details ...",
"no-organizations-found": "Keine Organisationen gefunden", "log_in": "Anmelden",
"no-runners-found": "Keine Läufer:innen gefunden", "log_in_to_your_account": "Bitte melde dich an",
"no-tracks-added-yet": "Es wurden noch keine Tracks erstellt.", "login_is_checked": "Login wird überprüft",
"organization": "Organisation", "logout": "Abmelden",
"organization-added": "Organisation hinzugefügt", "mail-validation-in-progress": "E-Mail Verifizierung läuft... ",
"organization-deleted": "Organisation gelöscht", "manage-admin-users": "Nutzer verwalten",
"organization-detail-is-being-loaded": "Organisationsdetails werden geladen ...", "middle-name": "Mittelname",
"organization-is-being-added": "Organisation wird hinzugefügt ...", "minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)",
"organization-name-is-required": "Der Name muss angegeben werden", "minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein",
"organizations": "Organisationen", "name": "Name",
"organizations-are-being-loaded": "Organisationen werden geladen ...", "name-is-required": "Der Gruppenname muss angegeben werden",
"orgs": "Organisationen", "new-password": "Neues Passwort",
"oss_credit_description": "Wir verwenden eine Menge Open Source-Software bei diesen Projekten und möchten uns bei den folgenden Projekten und Mitwirkenden bedanken, die dazu beitragen, Open Source großartig zu machen!", "no-contact-found": "Keine Kontakte gefunden",
"password": "Passwort", "no-contact-selected": "Kein Kontakt ausgewählt",
"password-changed": "Passwort wurde aktualisiert!", "no-contact-specified": "Kein Kontakt angegeben",
"password-is-required": "Passwort muss angegeben werden", "no-donors-found": "Keine Spender:innen gefunden",
"password-reset-failed": "Passwort zurücksetzen ist fehlgeschlagen!", "no-license-text-could-be-found": "Kein Lizenz-Text gefunden 😢",
"password-reset-in-progress": "Passwort wird zurückgesetzt...", "no-organization-or-team-found": "Keine Organisationen oder Teams gefunden",
"password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.", "no-organization-specified": "Keine Organisation angegeben",
"password-reset-successful": "Passwort erfolgreich zurückgesetzt!", "no-organizations-found": "Keine Organisationen gefunden",
"passwords-dont-match": "Die Passwörter stimmen nicht überein.", "no-runners-found": "Keine Läufer:innen gefunden",
"pdf-generation-failed": "PDF Generierung fehlgeschlagen!", "no-tracks-added-yet": "Es wurden noch keine Tracks erstellt.",
"pdf-successfully-generated": "PDF wurde erfolgreich generiert!", "non-blanko": "Keine/Blankokarte",
"pdfs-successfully-generated": "Alle PDFs wurden generiert!", "organization": "Organisation",
"per-kilometer": "pro Kilometer", "organization-added": "Organisation hinzugefügt",
"permissions": "Berechtigungen", "organization-deleted": "Organisation gelöscht",
"permissions-updated": "Berechtigungen aktualisiert!", "organization-detail-is-being-loaded": "Organisationsdetails werden geladen ...",
"phone": "Telefon", "organization-is-being-added": "Organisation wird hinzugefügt ...",
"please-copy-the-token-and-store-it-somewhere-save": "Bitte kopiere dir den Token und bewahre ihn gut auf.", "organization-name-is-required": "Der Name muss angegeben werden",
"please-provide-a-password": "Bitte gebe ein Passwort an...", "organizations": "Organisationen",
"please-provide-the-nessecary-information-to-add-a-new-donor": "Bitte mach die Notwendigen Angaben, um eine neue Sponsor:in zu erstellen", "organizations-are-being-loaded": "Organisationen werden geladen ...",
"please-provide-the-nessecary-information-to-create-a-new-donation": "Bitte gebe alle für das Sponsoring notwendigen Daten an.", "orgs": "Organisationen",
"please-provide-the-nessecary-information-to-create-a-new-scan": "Bitte gebe alle notwendigen Informationen an, um einen neuen Scan zu erstellen.", "oss_credit_description": "Wir verwenden eine Menge Open Source-Software bei diesen Projekten und möchten uns bei den folgenden Projekten und Mitwirkenden bedanken, die dazu beitragen, Open Source großartig zu machen!",
"please-provide-the-required-csv-xlsx-file": "Bitte eine CSV oder XLSX Datei hochladen.", "password": "Passwort",
"please-provide-the-required-information-for-creating-a-new-user-group": "Bitte gebe alle für eine neue Gruppe notwendigen Informationen an.", "password-changed": "Passwort wurde aktualisiert!",
"please-provide-the-required-information-to-add-a-new-contact": "Bitte gebe alle nötigen Informationen an, im den neuen Kontakt zu erstellen.", "password-is-required": "Passwort muss angegeben werden",
"please-provide-the-required-information-to-add-a-new-organization": "Bitte gebe alle nötigen Informationen an, im die neue Organisation zu erstellen.", "password-reset-failed": "Passwort zurücksetzen ist fehlgeschlagen!",
"please-provide-the-required-information-to-add-a-new-runner": "Bitte die benötigten Informationen angeben.", "password-reset-in-progress": "Passwort wird zurückgesetzt...",
"please-provide-the-required-information-to-add-a-new-team": "Bitte gebe alle nötigen Informationen an, im das neue Team zu erstellen.", "password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.",
"please-provide-the-required-information-to-add-a-new-track": "Bitte die benötigten Informationen angeben.", "password-reset-successful": "Passwort erfolgreich zurückgesetzt!",
"please-provide-the-required-information-to-add-a-new-user": "Bitte gebe alle nötigen Informationen an, im die neue Benutzer:in zu erstellen.", "passwords-dont-match": "Die Passwörter stimmen nicht überein.",
"please-provide-the-required-information-to-create-a-new-scanstation": "Bitte gebe alle für eine Scannerstation notwendigen Informationen an", "pdf-generation-failed": "PDF Generierung fehlgeschlagen!",
"please-request-a-new-reset-mail": "Bitte eine neue Passwortreset-Mail anfordern...", "pdf-successfully-generated": "PDF wurde erfolgreich generiert!",
"privacy": "Datenschutz", "pdfs-successfully-generated": "Alle PDFs wurden generiert!",
"privacy-loading": "Datenschutzerklärung lädt...", "per-kilometer": "pro Kilometer",
"profile": "Profil", "permissions": "Berechtigungen",
"profile-picture": "Profilbild", "permissions-updated": "Berechtigungen aktualisiert!",
"profile-updated": "Profil wurde aktualisiert!", "phone": "Telefon",
"read-license": "Lizenz-Text lesen", "please-copy-the-token-and-store-it-somewhere-save": "Bitte kopiere dir den Token und bewahre ihn gut auf.",
"receipt-needed": "Spendenquittung benötigt", "please-provide-a-password": "Bitte gebe ein Passwort an...",
"repo_link": "Link", "please-provide-the-nessecary-information-to-add-a-new-donor": "Bitte mach die Notwendigen Angaben, um eine neue Sponsor:in zu erstellen",
"request-a-new-reset-mail": "Neue Reset-Mail anfordern", "please-provide-the-nessecary-information-to-create-a-new-donation": "Bitte gebe alle für das Sponsoring notwendigen Daten an.",
"reset-my-password": "Passwort zurücksetzen", "please-provide-the-nessecary-information-to-create-a-new-scan": "Bitte gebe alle notwendigen Informationen an, um einen neuen Scan zu erstellen.",
"reset-password": "Passwort zurücksetzen", "please-provide-the-required-csv-xlsx-file": "Bitte eine CSV oder XLSX Datei hochladen.",
"runner": "Läufer:in", "please-provide-the-required-information-for-creating-a-new-user-group": "Bitte gebe alle für eine neue Gruppe notwendigen Informationen an.",
"runner-added": "Läufer:in hinzugefügt", "please-provide-the-required-information-to-add-a-new-contact": "Bitte gebe alle nötigen Informationen an, im den neuen Kontakt zu erstellen.",
"runner-import": "Läufer:innen Import", "please-provide-the-required-information-to-add-a-new-organization": "Bitte gebe alle nötigen Informationen an, im die neue Organisation zu erstellen.",
"runner-is-being-added": "Läufer:in wird hinzugefügt...", "please-provide-the-required-information-to-add-a-new-runner": "Bitte die benötigten Informationen angeben.",
"runner-updated": "Läufer:in aktualisiert!", "please-provide-the-required-information-to-add-a-new-team": "Bitte gebe alle nötigen Informationen an, im das neue Team zu erstellen.",
"runnerimport_verify_runners_org": "Bitte die Läufer:innen für den Import in die Organisation \"{org_name}\" bestätigen", "please-provide-the-required-information-to-add-a-new-track": "Bitte die benötigten Informationen angeben.",
"runners": "Läufer", "please-provide-the-required-information-to-add-a-new-user": "Bitte gebe alle nötigen Informationen an, im die neue Benutzer:in zu erstellen.",
"runners-are-being-imported": "Läufer:innen werden importiert ...", "please-provide-the-required-information-to-create-a-new-scanstation": "Bitte gebe alle für eine Scannerstation notwendigen Informationen an",
"runners-are-being-loaded": "Läufer:innen werden geladen ...", "please-request-a-new-reset-mail": "Bitte eine neue Passwortreset-Mail anfordern...",
"save": "Speichern", "privacy": "Datenschutz",
"save-changes": "Änderungen speichern", "privacy-loading": "Datenschutzerklärung lädt...",
"scan-added": "Scan hinzugefügt", "profile": "Profil",
"scan-is-being-updated": "Scan wird aktualisiert", "profile-picture": "Profilbild",
"scan-with-fixed-distance": "Scan mit Festdistanz", "profile-updated": "Profil wurde aktualisiert!",
"scans": "Scans", "read-license": "Lizenz-Text lesen",
"scans-are-being-loaded": "Scans werden geladen", "receipt-needed": "Spendenquittung benötigt",
"scanstation": "Scanner Station", "repo_link": "Link",
"scanstation-added": "Station wurde erstellt", "request-a-new-reset-mail": "Neue Reset-Mail anfordern",
"scanstation-is-being-added": "Scannerstation wird angelegt...", "reset-my-password": "Passwort zurücksetzen",
"scanstations": "Scanner Stationen", "reset-password": "Passwort zurücksetzen",
"scanstations-are-being-loaded": "Scannerstationen werden geladen...", "runner": "Läufer:in",
"search-for-an-organization-by-name-or-id": "Suche eine Organisation (via Name oder Id)", "runner-added": "Läufer:in hinzugefügt",
"search-for-an-organization-or-team-by-name-or-id": "Suche eine Organisation oder ein Team (via Name oder Id)", "runner-import": "Läufer:innen Import",
"search-for-donor-name-or-id": "Suche eine Spender:in (via Name oder Id)", "runner-is-being-added": "Läufer:in wird hinzugefügt...",
"search-for-permission": "Berechtigungen durchsuchen", "runner-updated": "Läufer:in aktualisiert!",
"search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)", "runnerimport_verify_runners_org": "Bitte die Läufer:innen für den Import in die Organisation \"{org_name}\" bestätigen",
"select-all": "Alle auswählen", "runners": "Läufer",
"select-language": "Sprache auswählen", "runners-are-being-imported": "Läufer:innen werden importiert ...",
"send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services", "runners-are-being-loaded": "Läufer:innen werden geladen ...",
"set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen", "save": "Speichern",
"settings": "Einstellungen", "save-changes": "Änderungen speichern",
"settings-for-your-profile": "Die Einstellungen deines Accounts", "scan-added": "Scan hinzugefügt",
"something-about-the-group": "Infos zur Gruppe", "scan-is-being-updated": "Scan wird aktualisiert",
"stats-are-being-loaded": "Die Statistiken werden geladen...", "scan-with-fixed-distance": "Scan mit Festdistanz",
"status": "Status", "scans": "Scans",
"stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können", "scans-are-being-loaded": "Scans werden geladen",
"successful-password-reset": "Passwort erfolgreich zurückgesetzt!", "scanstation": "Scanner Station",
"team": "Team", "scanstation-added": "Station wurde erstellt",
"team-detail-is-being-loaded": "Team wird geladen...", "scanstation-is-being-added": "Scannerstation wird angelegt...",
"team-name": "Teamname", "scanstations": "Scanner Stationen",
"team-name-is-required": "Teamname ist erforderlich", "scanstations-are-being-loaded": "Scannerstationen werden geladen...",
"teams": "Teams", "search-for-an-organization-by-name-or-id": "Suche eine Organisation (via Name oder Id)",
"teams-are-being-loaded": "Teams werden geladen ...", "search-for-an-organization-or-team-by-name-or-id": "Suche eine Organisation oder ein Team (via Name oder Id)",
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number": "Die angegebene Telefonnummer ist nicht korrekt. <br /> Bitte gebe eine Telefonnummer im internationalen Format an...", "search-for-donor-name-or-id": "Suche eine Spender:in (via Name oder Id)",
"the-scans-distance-must-be-greater-than-0m": "Die Distanz muss größer als 0m sein.", "search-for-permission": "Berechtigungen durchsuchen",
"the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again": "Der Scannerstation Token wird nur einmal angezeigt - du kannst ihn nicht ändern oder ihn dir nochmal anzeigen lassen!", "search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)",
"there-are-no-contacts-added-yet": "Es wurden noch keine Kontakte hinzugefügt.", "select-all": "Alle auswählen",
"there-are-no-donations-yet": "Es gibt noch keine Sponsorings", "select-language": "Sprache auswählen",
"there-are-no-donors-yet": "Es gibt noch keine Sponsor:innen", "send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services",
"there-are-no-groups-yet": "Es gibt noch keine Gruppen", "set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen",
"there-are-no-organizations-added-yet": "Es wurden noch keine Organisationen hinzugefügt.", "settings": "Einstellungen",
"there-are-no-runners-added-yet": "Es wurden noch keine Läufer:innen hinzugefügt.", "settings-for-your-profile": "Die Einstellungen deines Accounts",
"there-are-no-scans-yet": "Es gibt noch keine Scans", "something-about-the-group": "Infos zur Gruppe",
"there-are-no-teams-added-yet": "Es wurden noch keine Teams hinzugefügt.", "stats-are-being-loaded": "Die Statistiken werden geladen...",
"there-are-no-users-added-yet": "Es wurden noch keine Benutzer hinzugefügt.", "status": "Status",
"this-might-take-a-moment": "Das könnte einen kleinen Moment dauern", "stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können",
"this-scanstation-is": "Diese Station ist", "successful-password-reset": "Passwort erfolgreich zurückgesetzt!",
"token": "Token", "team": "Team",
"total-distance": "gelaufene Strecke", "team-detail-is-being-loaded": "Team wird geladen...",
"total-donation-amount": "Gesamtbetrag", "team-name": "Teamname",
"total-donations": "Spendensumme", "team-name-is-required": "Teamname ist erforderlich",
"total-scans": "gesamte Scans", "teams": "Teams",
"track": "Track", "teams-are-being-loaded": "Teams werden geladen ...",
"track-added": "Track hinzugefügt", "the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number": "Die angegebene Telefonnummer ist nicht korrekt. <br /> Bitte gebe eine Telefonnummer im internationalen Format an...",
"track-data-is-being-loaded": "Trackdaten werden geladen", "the-scans-distance-must-be-greater-than-0m": "Die Distanz muss größer als 0m sein.",
"track-is-being-added": "Track wird hinzugefügt...", "the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again": "Der Scannerstation Token wird nur einmal angezeigt - du kannst ihn nicht ändern oder ihn dir nochmal anzeigen lassen!",
"track-length-in-m": "Tracklänge (in Metern)", "there-are-no-cards-yet": "Es gibt noch keine Läuferkarten.",
"track-length-must-be-greater-than-0": "Die Länge muss größer als 0 (Meter) sein", "there-are-no-contacts-added-yet": "Es wurden noch keine Kontakte hinzugefügt.",
"track-name": "Trackname", "there-are-no-donations-yet": "Es gibt noch keine Sponsorings",
"track-name-must-not-be-empty": "Der Name muss angegeben werden", "there-are-no-donors-yet": "Es gibt noch keine Sponsor:innen",
"tracks": "Tracks", "there-are-no-groups-yet": "Es gibt noch keine Gruppen",
"update-password": "Passwort ändern", "there-are-no-organizations-added-yet": "Es wurden noch keine Organisationen hinzugefügt.",
"updated-contact": "Kontakt aktualisiert!", "there-are-no-runners-added-yet": "Es wurden noch keine Läufer:innen hinzugefügt.",
"updated-donor": "Sponsor:in wurde aktualisiert", "there-are-no-scans-yet": "Es gibt noch keine Scans",
"updated-organization": "Organisation wurde aktualisiert", "there-are-no-teams-added-yet": "Es wurden noch keine Teams hinzugefügt.",
"updated-scan": "Scan wurde aktualisiert", "there-are-no-users-added-yet": "Es wurden noch keine Benutzer hinzugefügt.",
"updateing-group": "Gruppe wird aktualisiert...", "this-card-is": "Diese Karte ist",
"updating-organization": "Organisation wird aktualisiert", "this-might-take-a-moment": "Das könnte einen kleinen Moment dauern",
"updating-permissions": "Berechtigungen werden aktualisiert...", "this-scanstation-is": "Diese Station ist",
"updating-runner": "Läufer:in wird aktualisiert.", "token": "Token",
"updating-user": "Benutzer:in wird aktualisiert...", "total-distance": "gelaufene Strecke",
"updating-your-profile": "Profil wird aktualisiert...", "total-donation-amount": "Gesamtbetrag",
"user-added": "Benutzer hinzugefügt", "total-donations": "Spendensumme",
"user-groups": "Benutzergruppen", "total-scans": "gesamte Scans",
"user-is-being-added": "Benutzer wird hinzugefügt ...", "track": "Track",
"user-updated": "Benutzer:in wurde aktualisiert", "track-added": "Track hinzugefügt",
"username": "Benutzername", "track-data-is-being-loaded": "Trackdaten werden geladen",
"users": "Benutzer", "track-is-being-added": "Track wird hinzugefügt...",
"valid": "Gültig", "track-length-in-m": "Tracklänge (in Metern)",
"valid-city-is-required": "Du musst eine Stadt angeben", "track-length-must-be-greater-than-0": "Die Länge muss größer als 0 (Meter) sein",
"valid-email-is-required": "Es wird eine valide E-Mail Adresse benötigt", "track-name": "Trackname",
"valid-international-phone-number-is-required": "Du musst eine Telefonnummer im internationalen Format angeben...", "track-name-must-not-be-empty": "Der Name muss angegeben werden",
"valid-zipcode-postal-code-is-required": "Du musst eine valide Postleitzahl angeben", "tracks": "Tracks",
"verfuegbare": "Verfügbar", "update-password": "Passwort ändern",
"welcome_wavinghand": "Willkommen 👋", "updated-contact": "Kontakt aktualisiert!",
"yes-i-copied-the-token": "Ja, ich habe den Token kopiert", "updated-donor": "Sponsor:in wurde aktualisiert",
"you-are-going-to-loose-all-permissions-and-access-to-the-runner-system": "Du wirst all deine Berechtigungen und den Zugriff aufs Läufersystem verlieren!", "updated-organization": "Organisation wurde aktualisiert",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "Du kannst dich jetzt mit deinem neuen Passwort anmelden! 🎉", "updated-scan": "Scan wurde aktualisiert",
"you-dont-have-any-scanstations-yet": "Es gibt noch keine Scannerstationen", "updateing-group": "Gruppe wird aktualisiert...",
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben", "updating-card": "Karte wird aktualisiert",
"zip-postal-code": "Postleitzahl" "updating-organization": "Organisation wird aktualisiert",
} "updating-permissions": "Berechtigungen werden aktualisiert...",
"updating-runner": "Läufer:in wird aktualisiert.",
"updating-user": "Benutzer:in wird aktualisiert...",
"updating-your-profile": "Profil wird aktualisiert...",
"user-added": "Benutzer hinzugefügt",
"user-groups": "Benutzergruppen",
"user-is-being-added": "Benutzer wird hinzugefügt ...",
"user-updated": "Benutzer:in wurde aktualisiert",
"username": "Benutzername",
"users": "Benutzer",
"valid": "Gültig",
"valid-city-is-required": "Du musst eine Stadt angeben",
"valid-email-is-required": "Es wird eine valide E-Mail Adresse benötigt",
"valid-international-phone-number-is-required": "Du musst eine Telefonnummer im internationalen Format angeben...",
"valid-zipcode-postal-code-is-required": "Du musst eine valide Postleitzahl angeben",
"verfuegbare": "Verfügbar",
"welcome_wavinghand": "Willkommen 👋",
"yes-i-copied-the-token": "Ja, ich habe den Token kopiert",
"you-are-going-to-loose-all-permissions-and-access-to-the-runner-system": "Du wirst all deine Berechtigungen und den Zugriff aufs Läufersystem verlieren!",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "Du kannst dich jetzt mit deinem neuen Passwort anmelden! 🎉",
"you-can-provide-a-runner-but-you-dont-have-to": "Du kannst eine Läufer:in angeben, musst aber nicht.",
"you-dont-have-any-scanstations-yet": "Es gibt noch keine Scannerstationen",
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben",
"you-must-create-at-least-one-card-or-cancel": "Du musst mindestens eine Blankokarte erstellen (oder abbrechen).",
"zip-postal-code": "Postleitzahl",
"selfservice-registration": "Selfservice Registrierung",
"copied-link-to-clipboard": "Link wurde in die Zwischenablage kopiert",
"click-to-copy-the-link-into-your-clipboard": "Klicke auf den Link, um ihn in deine Zwischenablage zu kopieren"
}

View File

@@ -1,392 +1,421 @@
{ {
"404message": "Sorry, the page you are looking for could not be found.", "404message": "Sorry, the page you are looking for could not be found.",
"404title": "Error 404", "404title": "Error 404",
"about": "About", "about": "About",
"action": "Action", "action": "Action",
"active": "Active", "active": "Active",
"add-donation": "Add donation", "add-card": "Add Card",
"add-donor": "add donor", "add-donation": "Add donation",
"add-scan": "Add scan", "add-donor": "add donor",
"add-the-first-scanstation": "Add your first scanstation.", "add-scan": "Add scan",
"add-user-group": "Add User Group", "add-the-first-scanstation": "Add your first scanstation.",
"add-your-first-contact": "Add your first contact", "add-user-group": "Add User Group",
"add-your-first-donor": "add your first donor", "add-your-first-card": "Add your first card",
"add-your-first-group": "Add your first group", "add-your-first-contact": "Add your first contact",
"add-your-first-organization": "Add your first organization", "add-your-first-donor": "add your first donor",
"add-your-first-runner": "Add your first runner", "add-your-first-group": "Add your first group",
"add-your-first-team": "Add your first team", "add-your-first-organization": "Add your first organization",
"add-your-first-track": "Add your first track.", "add-your-first-runner": "Add your first runner",
"add-your-first-user": "Add your first user", "add-your-first-team": "Add your first team",
"add-your-fist-donation": "Add your fist donation", "add-your-first-track": "Add your first track.",
"add-your-fist-scan": "Add your fist scan", "add-your-first-user": "Add your first user",
"adding-scan": "Adding Scan", "add-your-fist-donation": "Add your fist donation",
"address": "Address", "add-your-fist-scan": "Add your fist scan",
"address-is-required": "Address is required", "adding-card": "Adding Card",
"after-deletion-we-cant-restore-your-old-profile": "After deletion we can't restore your old profile!", "adding-scan": "Adding Scan",
"after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "After the update you'll get logged out - Please login with your new password after that.", "address": "Address",
"all-associated-donations-will-get-deleted-as-well": "All associated donations will get deleted as well", "address-is-required": "Address is required",
"all-associated-runners-will-be-deleted-too": "All associated runners will be deleted too!", "after-deletion-we-cant-restore-your-old-profile": "After deletion we can't restore your old profile!",
"all-associated-teams-and-runners-will-be-deleted-too": "All associated teams and runners will be deleted too!", "after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "After the update you'll get logged out - Please login with your new password after that.",
"amount-per-kilometer": "Amount per kilometer", "all-associated-donations-will-get-deleted-as-well": "All associated donations will get deleted as well",
"apartment-suite-etc": "Apartment, suite, etc.", "all-associated-runners-will-be-deleted-too": "All associated runners will be deleted too!",
"application_name": "Lauf für Kaya! - Admin", "all-associated-teams-and-runners-will-be-deleted-too": "All associated teams and runners will be deleted too!",
"applying-changes": "Applying Changes", "amount": "Amount",
"attention": "Attention!", "amount-per-kilometer": "Amount per kilometer",
"author": "Author", "apartment-suite-etc": "Apartment, suite, etc.",
"bitte-bestaetige-diese-laeufer-fuer-den-import": "Please confirm these runners for import.", "application_name": "Lauf für Kaya! - Admin",
"by": "by", "applying-changes": "Applying Changes",
"cancel": "Cancel", "attention": "Attention!",
"cancel-delete": "Cancel Delete", "author": "Author",
"cancel-keep-donor": "Cancel, keep donor", "bitte-bestaetige-diese-laeufer-fuer-den-import": "Please confirm these runners for import.",
"cancel-keep-my-profile": "Cancel, keep my profile", "by": "by",
"cancel-keep-organization": "Cancel, keep organization", "cancel": "Cancel",
"cancel-keep-team": "Cancel, keep team", "cancel-delete": "Cancel Delete",
"cannot-reset-your-password-directly": "Bummer. We unfortunately cannot reset your password directly. Please send us a mail and confirm your identity", "cancel-keep-donor": "Cancel, keep donor",
"change-your-password-here": "Change your password here", "cancel-keep-my-profile": "Cancel, keep my profile",
"changing-your-password": "Changing your password", "cancel-keep-organization": "Cancel, keep organization",
"city": "City", "cancel-keep-team": "Cancel, keep team",
"click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard", "cannot-reset-your-password-directly": "Bummer. We unfortunately cannot reset your password directly. Please send us a mail and confirm your identity",
"close": "Close", "card-added": "Card added",
"configure-the-tracks-and-minimum-lap-times": "configure the tracks & minimum lap times", "card-deleted": "Card deleted",
"confirm": "Confirm", "card-updated": "Card updated",
"confirm-delete": "Confirm Delete", "cards": "Cards",
"confirm-delete-donor-with-all-donations": "Confirm, delete donor with all donations", "change-your-password-here": "Change your password here",
"confirm-delete-my-user-profile": "Confirm, delete my user profile", "changing-your-password": "Changing your password",
"confirm-delete-organization-and-associated-teams-runners": "Confirm, delete organization and associated teams+runners.", "city": "City",
"confirm-delete-team-and-associated-runners": "Confirm, delete team and associated runners.", "click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard",
"confirm-deletion": "Confirm Deletion", "close": "Close",
"confirm-the-new-password": "Confirm the new password", "code": "Code",
"contact": "Contact", "configure-the-tracks-and-minimum-lap-times": "configure the tracks & minimum lap times",
"contact-deleted": "Contact deleted", "confirm": "Confirm",
"contact-information": "Contact Information", "confirm-delete": "Confirm Delete",
"contact-is-being-updated": "Contact is being updated...", "confirm-delete-donor-with-all-donations": "Confirm, delete donor with all donations",
"contact-is-not-a-member-in-any-group": "Contact is not a member in any group", "confirm-delete-my-user-profile": "Confirm, delete my user profile",
"contacts": "Contacts", "confirm-delete-organization-and-associated-teams-runners": "Confirm, delete organization and associated teams+runners.",
"contacts-are-being-loaded": "contacts are being loaded...", "confirm-delete-team-and-associated-runners": "Confirm, delete team and associated runners.",
"copied-token-to-clipboard": "Copied token to clipboard", "confirm-deletion": "Confirm Deletion",
"count_organizations": "# Organizations", "confirm-the-new-password": "Confirm the new password",
"count_teams": "# Teams", "contact": "Contact",
"create": "Create", "contact-deleted": "Contact deleted",
"create-a-new": "Create a new", "contact-information": "Contact Information",
"create-a-new-contact": "Create a new contact", "contact-is-being-updated": "Contact is being updated...",
"create-a-new-distance-donation": "Create a new distance donation", "contact-is-not-a-member-in-any-group": "Contact is not a member in any group",
"create-a-new-donor": "Create a new donor", "contacts": "Contacts",
"create-a-new-fixed-donation": "Create a new fixed donation", "contacts-are-being-loaded": "contacts are being loaded...",
"create-a-new-organization": "Create a new Organization", "copied-token-to-clipboard": "Copied token to clipboard",
"create-a-new-runner": "Create a new Runner", "count_organizations": "# Organizations",
"create-a-new-scan-fixed-only": "Create a new scan (fixed only)", "count_teams": "# Teams",
"create-a-new-scanstation": "Create a new station", "create": "Create",
"create-a-new-team": "Create a new team", "create-a-new": "Create a new",
"create-a-new-track": "Create a new Track", "create-a-new-card": "Create a new card",
"create-a-new-user": "Create a new User", "create-a-new-contact": "Create a new contact",
"create-a-new-user-group": "Create a new user group", "create-a-new-distance-donation": "Create a new distance donation",
"create-organization": "Create Organization", "create-a-new-donor": "Create a new donor",
"create-team": "Create Team", "create-a-new-fixed-donation": "Create a new fixed donation",
"create-track": "Create Track", "create-a-new-organization": "Create a new Organization",
"create-user": "Create User", "create-a-new-runner": "Create a new Runner",
"credits": "Credits", "create-a-new-scan-fixed-only": "Create a new scan (fixed only)",
"csv_import__class": "Class", "create-a-new-scanstation": "Create a new station",
"csv_import__firstname": "Firstname", "create-a-new-team": "Create a new team",
"csv_import__lastname": "Lastname", "create-a-new-track": "Create a new Track",
"csv_import__middlename": "Middlename", "create-a-new-user": "Create a new User",
"csv_import__team": "Team", "create-a-new-user-group": "Create a new user group",
"danger-zone": "Danger zone", "create-bulk-blanco-cards": "Create bulk blanco cards",
"dashboard-greeting": "Hello", "create-bulk-cards": "Add blanco cards",
"dashboard-title": "Dashboard", "create-organization": "Create Organization",
"datatable": { "create-team": "Create Team",
"search": "🔍 Search...", "create-track": "Create Track",
"sort_column_ascending": "Sort column ascending", "create-user": "Create User",
"sort_column_descending": "Sort column descending", "created-blanco-cards": "Created blanco cards",
"previous": "Previous", "creating-blanco-cards": "Creating blanco cards",
"next": "Next", "credits": "Credits",
"page": "Page", "csv_import__class": "Class",
"showing": "Showing", "csv_import__firstname": "Firstname",
"records": "Records", "csv_import__lastname": "Lastname",
"of": "of", "csv_import__middlename": "Middlename",
"to": "to", "csv_import__team": "Team",
"loading": "Loading...", "danger-zone": "Danger zone",
"no_matching_records_found": "No matching records found", "dashboard-greeting": "Hello",
"an_error_happened_while_fetching_the_data": "An error happened while fetching the data" "dashboard-title": "Dashboard",
}, "datatable": {
"delete": "Delete", "search": "🔍 Search...",
"delete-contact": "Delete Contact", "sort_column_ascending": "Sort column ascending",
"delete-donation": "Delete Donation", "sort_column_descending": "Sort column descending",
"delete-donor": "Delete donor", "previous": "Previous",
"delete-group": "Delete Group", "next": "Next",
"delete-organization": "Delete Organization", "page": "Page",
"delete-profile": "Delete Profile", "showing": "Showing",
"delete-runner": "Delete Runner", "records": "Records",
"delete-scan": "Delete scan", "of": "of",
"delete-station": "Delete station", "to": "to",
"delete-team": "Delete Team", "loading": "Loading...",
"delete-user": "Delete User", "no_matching_records_found": "No matching records found",
"deleted-scan": "Deleted scan", "an_error_happened_while_fetching_the_data": "An error happened while fetching the data"
"dependency_name": "Name", },
"description": "description", "delete": "Delete",
"description-optional": "Description (optional)", "delete-contact": "Delete Contact",
"deselect-all": "deselect all", "delete-donation": "Delete Donation",
"details": "Details", "delete-donor": "Delete donor",
"disabled": "disabled", "delete-group": "Delete Group",
"distance": "Distance", "delete-organization": "Delete Organization",
"distance-donation": "distance donation", "delete-profile": "Delete Profile",
"distance-in-km": "Distance in km", "delete-runner": "Delete Runner",
"distance-track": "Distance (+Track)", "delete-scan": "Delete scan",
"do-you-really-want-to-delete-your-profile": "Do you really want to delete your profile?", "delete-station": "Delete station",
"do-you-want-to-delete-the-organization-delete_org-name": "Do you want to delete the organization {orgname}?", "delete-team": "Delete Team",
"do-you-want-to-delete-the-team-delete_team-name": "Do you want to delete the team {teamname}?", "delete-user": "Delete User",
"do-you-want-to-delete-this-donor-with-all-related-donations": "Do you want to delete this donor with all related donations", "deleted-scan": "Deleted scan",
"documentation": "Documentation", "dependency_name": "Name",
"donation-amount": "Donation amount", "description": "description",
"donation-amount-must-be-greater-that-0-00eur": "Donation amount must be greater that 0.00€", "description-optional": "Description (optional)",
"donations": "Donations", "deselect-all": "deselect all",
"donor": "Donor", "details": "Details",
"donor-added": "Donor added", "disabled": "disabled",
"donor-deleted": "donor deleted", "distance": "Distance",
"donor-has-no-associated-donations": "Donor has no associated donations.", "distance-donation": "distance donation",
"donor-is-being-added": "Donor is being added...", "distance-in-km": "Distance in km",
"donor-is-being-updated": "Donor is being updated", "distance-track": "Distance (+Track)",
"donors": "Donors", "do-you-really-want-to-delete-your-profile": "Do you really want to delete your profile?",
"donors-are-being-loaded": "donors are being loaded", "do-you-want-to-delete-the-organization-delete_org-name": "Do you want to delete the organization {orgname}?",
"dont-have-your-email-connected": "Don't have your email connected?", "do-you-want-to-delete-the-team-delete_team-name": "Do you want to delete the team {teamname}?",
"dont-panic-were-resetting-it": "Don't panic, we're resetting it ✌", "do-you-want-to-delete-this-donor-with-all-related-donations": "Do you want to delete this donor with all related donations",
"e-mail-adress": "E-Mail Adress", "documentation": "Documentation",
"edit": "Edit", "donation-amount": "Donation amount",
"edit-permissions": "edit permissions", "donation-amount-must-be-greater-that-0-00eur": "Donation amount must be greater that 0.00€",
"email_address_or_username": "Email / username", "donations": "Donations",
"enabled": "enabled", "donor": "Donor",
"enabled_large": "Enabled", "donor-added": "Donor added",
"english": "English", "donor-deleted": "donor deleted",
"error-during-import": "Error during import", "donor-has-no-associated-donations": "Donor has no associated donations.",
"error-whyile-copying-to-clipboard": "Error while copying to clipboard", "donor-is-being-added": "Donor is being added...",
"error_on_login": "Error on login", "donor-is-being-updated": "Donor is being updated",
"erteilte": "Directly granted", "donors": "Donors",
"everything-concerning-your-profile": "Everything concerning your profile", "donors-are-being-loaded": "donors are being loaded",
"everything-is-more-fun-together": "everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️", "dont-have-your-email-connected": "Don't have your email connected?",
"faq": "FAQ", "dont-panic-were-resetting-it": "Don't panic, we're resetting it ✌",
"filter-by-organization-team": "Filter by Organization/ Team", "e-mail-adress": "E-Mail Adress",
"first-name": "First name", "edit": "Edit",
"first-name-is-required": "First Name is required", "edit-a-card": "Edit a card",
"first-scan-of-the-day": "First scan of the day.", "edit-permissions": "edit permissions",
"fixed-donation": "fixed donation", "email_address_or_username": "Email / username",
"forgot_password": "Forgot your password?", "enabled": "enabled",
"geerbte": "inherited", "enabled_large": "Enabled",
"general-stats": "General Stats", "english": "English",
"general_promise_error": "😢 Error", "error-during-import": "Error during import",
"generate-sponsoring-contract": "generate sponsoring contract", "error-whyile-copying-to-clipboard": "Error while copying to clipboard",
"generate-sponsoring-contracts": "generate sponsoring contracts", "error_on_login": "Error on login",
"generating-pdf": "generating PDF...", "erteilte": "Directly granted",
"generating-pdfs": "generating PDFs...", "everything-concerning-your-profile": "Everything concerning your profile",
"generic-ui-logic-error": "Something went wrong in the UI logic", "everything-is-more-fun-together": "everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️",
"german": "German", "faq": "FAQ",
"go-to-login": "Go To Login", "filter-by-organization-team": "Filter by Organization/ Team",
"goback": "Go Home", "first-name": "First name",
"granted": "granted", "first-name-is-required": "First Name is required",
"group": "Group", "first-scan-of-the-day": "First scan of the day.",
"group-added": "Group added", "fixed-donation": "fixed donation",
"group-is-being-added": "Group is being added...", "forgot_password": "Forgot your password?",
"group-name-is-required": "Group name is required", "geerbte": "inherited",
"group-updated": "group updated", "general-stats": "General Stats",
"groups": "Groups", "general_promise_error": "😢 Error",
"groups-are-being-loaded": "Groups are being loaded", "generate-runnercards": "Generate Runnercards",
"home": "Home", "generate-sponsoring-contract": "generate sponsoring contract",
"icon-image-credits": "We also want to thank these projects for illustrations and icons:", "generate-sponsoring-contracts": "generate sponsoring contracts",
"import-finished": "Import finished", "generating-pdf": "generating PDF...",
"import-runners": "Import runners", "generating-pdfs": "generating PDFs...",
"import__target-organization": "Target Organization", "generic-ui-logic-error": "Something went wrong in the UI logic",
"imprint": "Imprint", "german": "German",
"imprint-loading": "Imprint loading...", "go-to-login": "Go To Login",
"inactive": "Inactive", "goback": "Go Home",
"installed-version": "Installed version", "granted": "granted",
"internal-error": "Internal Error", "group": "Group",
"invalid": "Invalid", "group-added": "Group added",
"invalid-mail-reset": "the provided email is invalid", "group-is-being-added": "Group is being added...",
"laeufer-hinzufuegen": "Add runner", "group-name-is-required": "Group name is required",
"laeufer-importieren": "Läufer importieren", "group-updated": "group updated",
"laptime": "Laptime", "groups": "Groups",
"last-name": "Last name", "groups-are-being-loaded": "Groups are being loaded",
"last-name-is-required": "Last Name is required", "home": "Home",
"lfk-is-os": "The \"Lauf für Kaya!\" Frontend is (like all other projects for the \"LfK!\" Also) an open source project.", "icon-image-credits": "We also want to thank these projects for illustrations and icons:",
"license": "License", "if-you-want-to-create-multiple-blanco-cards-try-the-add-bulk-button": "If you want to create multiple blanco cards: Try the 'Add blanco cards' button.",
"licenses-are-being-loaded": "Licenses are being loaded...", "import-finished": "Import finished",
"loading-contact-details": "Loading contact details...", "import-runners": "Import runners",
"loading-donation-details": "Loading donation details", "import__target-organization": "Target Organization",
"loading-donor-details": "Loading donor details", "imprint": "Imprint",
"loading-group-detail": "Loading group detail...", "imprint-loading": "Imprint loading...",
"loading-profile-data": "Loading profile data", "inactive": "Inactive",
"loading-runners": "loading runners...", "installed-version": "Installed version",
"loading-station-details": "Loading station details", "internal-error": "Internal Error",
"log_in": "Log in", "invalid": "Invalid",
"log_in_to_your_account": "Log in to your account", "invalid-mail-reset": "the provided email is invalid",
"login_is_checked": "Login is being checked...", "just-enter-how-many-you-want-and-the-system-will-create-them": "Just enter how many you want and the system will create them",
"logout": "Logout", "laeufer-hinzufuegen": "Add runner",
"mail-validation-in-progress": "mail validation in progress...", "laeufer-importieren": "Läufer importieren",
"manage-admin-users": "manage admin users", "laptime": "Laptime",
"middle-name": "Middle name", "last-name": "Last name",
"minimum-lap-time-in-s": "minimum lap time in s", "last-name-is-required": "Last Name is required",
"minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0", "lfk-is-os": "The \"Lauf für Kaya!\" Frontend is (like all other projects for the \"LfK!\" Also) an open source project.",
"name": "Name", "license": "License",
"name-is-required": "Name is required", "licenses-are-being-loaded": "Licenses are being loaded...",
"new-password": "New password", "loading-cards": "Loading cards",
"no-contact-found": "No contacts found", "loading-contact-details": "Loading contact details...",
"no-contact-selected": "No contact selected", "loading-donation-details": "Loading donation details",
"no-contact-specified": "no contact specified", "loading-donor-details": "Loading donor details",
"no-donors-found": "No donors found", "loading-group-detail": "Loading group detail...",
"no-license-text-could-be-found": "No license text could be found 😢", "loading-profile-data": "Loading profile data",
"no-organization-or-team-found": "No organization or team found", "loading-runners": "loading runners...",
"no-organization-specified": "no organization specified", "loading-station-details": "Loading station details",
"no-organizations-found": "No organizations found", "log_in": "Log in",
"no-runners-found": "No runners found", "log_in_to_your_account": "Log in to your account",
"no-tracks-added-yet": "there are no tracks added yet.", "login_is_checked": "Login is being checked...",
"organization": "Organization", "logout": "Logout",
"organization-added": "Organization added", "mail-validation-in-progress": "mail validation in progress...",
"organization-deleted": "Organization deleted", "manage-admin-users": "manage admin users",
"organization-detail-is-being-loaded": "organization detail is being loaded...", "middle-name": "Middle name",
"organization-is-being-added": "Organization is being added...", "minimum-lap-time-in-s": "minimum lap time in s",
"organization-name-is-required": "Organization name is required", "minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0",
"organizations": "Organizations", "name": "Name",
"organizations-are-being-loaded": "organizations are being loaded...", "name-is-required": "Name is required",
"orgs": "Organizations", "new-password": "New password",
"oss_credit_description": "We use a lot of open source software on these projects, and would like to thank the following projects and contributors who help make open source great!", "no-contact-found": "No contacts found",
"password": "Password", "no-contact-selected": "No contact selected",
"password-changed": "Password changed!", "no-contact-specified": "no contact specified",
"password-is-required": "Password is required", "no-donors-found": "No donors found",
"password-reset-failed": "Password reset failed!", "no-license-text-could-be-found": "No license text could be found 😢",
"password-reset-in-progress": "Password Reset in Progress...", "no-organization-or-team-found": "No organization or team found",
"password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".", "no-organization-specified": "no organization specified",
"password-reset-successful": "Password Reset successful!", "no-organizations-found": "No organizations found",
"passwords-dont-match": "Passwords don't match", "no-runners-found": "No runners found",
"pdf-generation-failed": "PDF generation failed!", "no-tracks-added-yet": "there are no tracks added yet.",
"pdf-successfully-generated": "PDF successfully generated!", "non-blanko": "Non/Blanko",
"pdfs-successfully-generated": "PDFs successfully generated!", "organization": "Organization",
"per-kilometer": "per Kilometer", "organization-added": "Organization added",
"permissions": "Permissions", "organization-deleted": "Organization deleted",
"permissions-updated": "Permissions updated!", "organization-detail-is-being-loaded": "organization detail is being loaded...",
"phone": "Phone", "organization-is-being-added": "Organization is being added...",
"please-copy-the-token-and-store-it-somewhere-save": "Please copy the token and store it somewhere safe.", "organization-name-is-required": "Organization name is required",
"please-provide-a-password": "Please provide a password...", "organizations": "Organizations",
"please-provide-the-nessecary-information-to-add-a-new-donor": "Please provide the nessecary information to add a new donor", "organizations-are-being-loaded": "organizations are being loaded...",
"please-provide-the-nessecary-information-to-create-a-new-donation": "Please provide the nessecary information to create a new donation", "orgs": "Organizations",
"please-provide-the-nessecary-information-to-create-a-new-scan": "Please provide the nessecary information to create a new scan.", "oss_credit_description": "We use a lot of open source software on these projects, and would like to thank the following projects and contributors who help make open source great!",
"please-provide-the-required-csv-xlsx-file": "Please provide the required csv/ xlsx file", "password": "Password",
"please-provide-the-required-information-for-creating-a-new-user-group": "Please provide the required information for creating a new user group.", "password-changed": "Password changed!",
"please-provide-the-required-information-to-add-a-new-contact": "Please provide the required information to add a new contact.", "password-is-required": "Password is required",
"please-provide-the-required-information-to-add-a-new-organization": "Please provide the required information to add a new organization.", "password-reset-failed": "Password reset failed!",
"please-provide-the-required-information-to-add-a-new-runner": "Please provide the required information to add a new runner.", "password-reset-in-progress": "Password Reset in Progress...",
"please-provide-the-required-information-to-add-a-new-team": "Please provide the required information to add a new team.", "password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".",
"please-provide-the-required-information-to-add-a-new-track": "Please provide the required information to add a new track.", "password-reset-successful": "Password Reset successful!",
"please-provide-the-required-information-to-add-a-new-user": "Please provide the required information to add a new user.", "passwords-dont-match": "Passwords don't match",
"please-provide-the-required-information-to-create-a-new-scanstation": "Please provide the required information to create a new scanstation", "pdf-generation-failed": "PDF generation failed!",
"please-request-a-new-reset-mail": "Please request a new reset mail...", "pdf-successfully-generated": "PDF successfully generated!",
"privacy": "Privacy", "pdfs-successfully-generated": "PDFs successfully generated!",
"privacy-loading": "Privacy loading...", "per-kilometer": "per Kilometer",
"profile": "Profile", "permissions": "Permissions",
"profile-picture": "Profile Picture", "permissions-updated": "Permissions updated!",
"profile-updated": "Profile updated!", "phone": "Phone",
"read-license": "Read License", "please-copy-the-token-and-store-it-somewhere-save": "Please copy the token and store it somewhere safe.",
"receipt-needed": "Receipt needed", "please-provide-a-password": "Please provide a password...",
"repo_link": "Link", "please-provide-the-nessecary-information-to-add-a-new-donor": "Please provide the nessecary information to add a new donor",
"request-a-new-reset-mail": "Request a new reset mail", "please-provide-the-nessecary-information-to-create-a-new-donation": "Please provide the nessecary information to create a new donation",
"reset-my-password": "Reset my password", "please-provide-the-nessecary-information-to-create-a-new-scan": "Please provide the nessecary information to create a new scan.",
"reset-password": "Reset your password", "please-provide-the-required-csv-xlsx-file": "Please provide the required csv/ xlsx file",
"runner": "Runner", "please-provide-the-required-information-for-creating-a-new-user-group": "Please provide the required information for creating a new user group.",
"runner-added": "Runner added", "please-provide-the-required-information-to-add-a-new-contact": "Please provide the required information to add a new contact.",
"runner-import": "Runner Import", "please-provide-the-required-information-to-add-a-new-organization": "Please provide the required information to add a new organization.",
"runner-is-being-added": "Runner is being added...", "please-provide-the-required-information-to-add-a-new-runner": "Please provide the required information to add a new runner.",
"runner-updated": "Runner updated!", "please-provide-the-required-information-to-add-a-new-team": "Please provide the required information to add a new team.",
"runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"", "please-provide-the-required-information-to-add-a-new-track": "Please provide the required information to add a new track.",
"runners": "Runners", "please-provide-the-required-information-to-add-a-new-user": "Please provide the required information to add a new user.",
"runners-are-being-imported": "Runners are being imported...", "please-provide-the-required-information-to-create-a-new-scanstation": "Please provide the required information to create a new scanstation",
"runners-are-being-loaded": "runners are being loaded...", "please-request-a-new-reset-mail": "Please request a new reset mail...",
"save": "Save", "privacy": "Privacy",
"save-changes": "Save Changes", "privacy-loading": "Privacy loading...",
"scan-added": "Scan added", "profile": "Profile",
"scan-is-being-updated": "Scan is being updated", "profile-picture": "Profile Picture",
"scan-with-fixed-distance": "Scan with fixed distance", "profile-updated": "Profile updated!",
"scans": "Scans", "read-license": "Read License",
"scans-are-being-loaded": "Scans are being loaded", "receipt-needed": "Receipt needed",
"scanstation": "Scanstation", "repo_link": "Link",
"scanstation-added": "Scanstation added", "request-a-new-reset-mail": "Request a new reset mail",
"scanstation-is-being-added": "Adding scanstation...", "reset-my-password": "Reset my password",
"scanstations": "Scanstations", "reset-password": "Reset your password",
"scanstations-are-being-loaded": "Loading scanstations...", "runner": "Runner",
"search-for-an-organization-by-name-or-id": "Search for an organization (by name or id)", "runner-added": "Runner added",
"search-for-an-organization-or-team-by-name-or-id": "Search for an organization or team (by name or id)", "runner-import": "Runner Import",
"search-for-donor-name-or-id": "Search for donor (by name or id)", "runner-is-being-added": "Runner is being added...",
"search-for-permission": "Search for permission", "runner-updated": "Runner updated!",
"search-for-runner-by-name-or-id": "Search for runner (by name or id)", "runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"",
"select-all": "select all", "runners": "Runners",
"select-language": "Select language", "runners-are-being-imported": "Runners are being imported...",
"send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services", "runners-are-being-loaded": "runners are being loaded...",
"set-the-user-active-inactive": "set the user active/ inactive", "save": "Save",
"settings": "Settings", "save-changes": "Save Changes",
"settings-for-your-profile": "Settings for your profile", "scan-added": "Scan added",
"something-about-the-group": "Something about the group...", "scan-is-being-updated": "Scan is being updated",
"stats-are-being-loaded": "stats are being loaded...", "scan-with-fixed-distance": "Scan with fixed distance",
"status": "Status", "scans": "Scans",
"stuff-that-could-harm-your-profile": "Stuff that could harm your profile", "scans-are-being-loaded": "Scans are being loaded",
"successful-password-reset": "Successful password reset!", "scanstation": "Scanstation",
"team": "Team", "scanstation-added": "Scanstation added",
"team-detail-is-being-loaded": "team detail is being loaded...", "scanstation-is-being-added": "Adding scanstation...",
"team-name": "Team name", "scanstations": "Scanstations",
"team-name-is-required": "team name is required", "scanstations-are-being-loaded": "Loading scanstations...",
"teams": "Teams", "search-for-an-organization-by-name-or-id": "Search for an organization (by name or id)",
"teams-are-being-loaded": "teams are being loaded...", "search-for-an-organization-or-team-by-name-or-id": "Search for an organization or team (by name or id)",
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number": "the provided phone number is invalid.<br />please enter a valid international number...", "search-for-donor-name-or-id": "Search for donor (by name or id)",
"the-scans-distance-must-be-greater-than-0m": "The scan's distance must be greater than 0m", "search-for-permission": "Search for permission",
"the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again": "The scanstation api token will only get displayed once - you won't be able to change or view it again!", "search-for-runner-by-name-or-id": "Search for runner (by name or id)",
"there-are-no-contacts-added-yet": "There are no contacts added yet.", "select-all": "select all",
"there-are-no-donations-yet": "There are no donations yet", "select-language": "Select language",
"there-are-no-donors-yet": "There are no donors yet", "send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services",
"there-are-no-groups-yet": "There are no groups yet", "set-the-user-active-inactive": "set the user active/ inactive",
"there-are-no-organizations-added-yet": "There are no organizations added yet.", "settings": "Settings",
"there-are-no-runners-added-yet": "There are no runners added yet.", "settings-for-your-profile": "Settings for your profile",
"there-are-no-scans-yet": "There are no scans yet", "something-about-the-group": "Something about the group...",
"there-are-no-teams-added-yet": "There are no teams added yet.", "stats-are-being-loaded": "stats are being loaded...",
"there-are-no-users-added-yet": "There are no users added yet.", "status": "Status",
"this-might-take-a-moment": "This might take a moment 👀", "stuff-that-could-harm-your-profile": "Stuff that could harm your profile",
"this-scanstation-is": "This scanstation is", "successful-password-reset": "Successful password reset!",
"token": "Token", "team": "Team",
"total-distance": "total distance", "team-detail-is-being-loaded": "team detail is being loaded...",
"total-donation-amount": "total donation amount", "team-name": "Team name",
"total-donations": "total donations", "team-name-is-required": "team name is required",
"total-scans": "total scans", "teams": "Teams",
"track": "Track", "teams-are-being-loaded": "teams are being loaded...",
"track-added": "Track added", "the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number": "the provided phone number is invalid.<br />please enter a valid international number...",
"track-data-is-being-loaded": "Track data is being loaded", "the-scans-distance-must-be-greater-than-0m": "The scan's distance must be greater than 0m",
"track-is-being-added": "Track is being added...", "the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again": "The scanstation api token will only get displayed once - you won't be able to change or view it again!",
"track-length-in-m": "Track Length in m", "there-are-no-cards-yet": "There are no cards yet.",
"track-length-must-be-greater-than-0": "Track length must be greater than 0", "there-are-no-contacts-added-yet": "There are no contacts added yet.",
"track-name": "Track name", "there-are-no-donations-yet": "There are no donations yet",
"track-name-must-not-be-empty": "Track name must not be empty", "there-are-no-donors-yet": "There are no donors yet",
"tracks": "Tracks", "there-are-no-groups-yet": "There are no groups yet",
"update-password": "Update password", "there-are-no-organizations-added-yet": "There are no organizations added yet.",
"updated-contact": "Updated contact!", "there-are-no-runners-added-yet": "There are no runners added yet.",
"updated-donor": "updated donor", "there-are-no-scans-yet": "There are no scans yet",
"updated-organization": "updated organization", "there-are-no-teams-added-yet": "There are no teams added yet.",
"updated-scan": "updated scan", "there-are-no-users-added-yet": "There are no users added yet.",
"updateing-group": "updateing group...", "this-card-is": "This card is",
"updating-organization": "updating organization", "this-might-take-a-moment": "This might take a moment 👀",
"updating-permissions": "updating permissions...", "this-scanstation-is": "This scanstation is",
"updating-runner": "Updating runner...", "token": "Token",
"updating-user": "updating user...", "total-distance": "total distance",
"updating-your-profile": "Updating your profile...", "total-donation-amount": "total donation amount",
"user-added": "User added", "total-donations": "total donations",
"user-groups": "User Groups", "total-scans": "total scans",
"user-is-being-added": "User is being added...", "track": "Track",
"user-updated": "User updated", "track-added": "Track added",
"username": "Username", "track-data-is-being-loaded": "Track data is being loaded",
"users": "Users", "track-is-being-added": "Track is being added...",
"valid": "Valid", "track-length-in-m": "Track Length in m",
"valid-city-is-required": "Valid city is required", "track-length-must-be-greater-than-0": "Track length must be greater than 0",
"valid-email-is-required": "valid email is required", "track-name": "Track name",
"valid-international-phone-number-is-required": "valid international phone number is required...", "track-name-must-not-be-empty": "Track name must not be empty",
"valid-zipcode-postal-code-is-required": "Valid zipcode/ postal code is required", "tracks": "Tracks",
"verfuegbare": "availdable", "update-card": "Update Card",
"welcome_wavinghand": "Welcome 👋", "update-password": "Update password",
"yes-i-copied-the-token": "Yes, I copied the token", "updated-contact": "Updated contact!",
"you-are-going-to-loose-all-permissions-and-access-to-the-runner-system": "You are going to loose all permissions and access to the runner system!", "updated-donor": "updated donor",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "You can now use your new password to log in to your account! 🎉", "updated-organization": "updated organization",
"you-dont-have-any-scanstations-yet": "You don't have any scanstations yet", "updated-scan": "updated scan",
"you-have-to-provide-an-organization": "You have to provide an organization", "updateing-group": "updateing group...",
"zip-postal-code": "ZIP/ postal code" "updating-card": "Updating card",
} "updating-organization": "updating organization",
"updating-permissions": "updating permissions...",
"updating-runner": "Updating runner...",
"updating-user": "updating user...",
"updating-your-profile": "Updating your profile...",
"user-added": "User added",
"user-groups": "User Groups",
"user-is-being-added": "User is being added...",
"user-updated": "User updated",
"username": "Username",
"users": "Users",
"valid": "Valid",
"valid-city-is-required": "Valid city is required",
"valid-email-is-required": "valid email is required",
"valid-international-phone-number-is-required": "valid international phone number is required...",
"valid-zipcode-postal-code-is-required": "Valid zipcode/ postal code is required",
"verfuegbare": "availdable",
"welcome_wavinghand": "Welcome 👋",
"yes-i-copied-the-token": "Yes, I copied the token",
"you-are-going-to-loose-all-permissions-and-access-to-the-runner-system": "You are going to loose all permissions and access to the runner system!",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "You can now use your new password to log in to your account! 🎉",
"you-can-provide-a-runner-but-you-dont-have-to": "You can provide a runner, but you don't have to.",
"you-dont-have-any-scanstations-yet": "You don't have any scanstations yet",
"you-have-to-provide-an-organization": "You have to provide an organization",
"you-must-create-at-least-one-card-or-cancel": "You must create at least one card (or cancel).",
"zip-postal-code": "ZIP/ postal code",
"selfservice-registration": "Selfservice registration",
"copied-link-to-clipboard": "Copied link to clipboard",
"click-to-copy-the-link-into-your-clipboard": "Click to copy the link into your clipboard"
}