Compare commits

...

51 Commits

Author SHA1 Message Date
f7fc1967a5 🚀RELEASE v0.10.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 20:07:14 +01:00
aedb8a765b new license file version [CI SKIP] 2021-03-26 19:06:59 +00:00
cf58bd15c3 Bumped lfk-client version 🔝
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 20:05:26 +01:00
34f4f68524 new license file version [CI SKIP] 2021-03-26 19:04:28 +00:00
09b8144080 Merge pull request 'Implemented password strength test feature/106-password_strength' (#115) from feature/106-password_strength into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #115
2021-03-26 19:03:03 +00:00
f1e6fb4ce7 Merge branch 'dev' into feature/106-password_strength 2021-03-26 19:59:47 +01:00
2ca63fd1f6 🚀RELEASE v0.9.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-26 19:59:29 +01:00
a5d25e7d92 Merge pull request 'Org selfservice Link feature/112-org_registration_links' (#114) from feature/112-org_registration_links into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #114
2021-03-26 18:58:34 +00:00
4167819e7a Formatting🛠
ref #106
2021-03-26 19:52:31 +01:00
5bd3a463f0 Sorted translations 🌍
ref #106
2021-03-26 19:51:57 +01:00
79c447b4c6 Added translations
ref #106
2021-03-26 19:51:27 +01:00
540304f947 User creation can now only be triggered if pw is strong enoug
ref #106
2021-03-26 19:48:42 +01:00
75d8f7331b Reset can now only be triggered if pw is strong enoug
ref #106
2021-03-26 19:47:26 +01:00
b2509e9e53 Module now exports functions that check if a password is strong enough and equal to a potential confirmation field
ref #106
2021-03-26 19:45:53 +01:00
7862f44653 Now using pw strength component for user creation
ref #106
2021-03-26 19:31:21 +01:00
962dd0c1bb Added missing exports
ref #106
2021-03-26 19:29:47 +01:00
5d5f7c7f5c Now using pw strength component for reset
ref #106
2021-03-26 19:29:37 +01:00
6aaf838451 Now using pw strength component
ref #106
2021-03-26 19:29:25 +01:00
ad3bd312e9 Added a password strength verification
ref #106
2021-03-26 19:26:26 +01:00
5fa9939696 Added more cirteria to the password strength component
ref #106
2021-03-26 19:02:09 +01:00
4956bb0e9c Implemented a custom password strength component
ref #106
2021-03-26 18:47:24 +01:00
c074c12be7 Sorted translations
ref #112
2021-03-26 18:11:49 +01:00
ddbc293e9c Added translations
ref #112
2021-03-26 18:11:23 +01:00
a3921b45c7 Copy now 100% worX
ref #112
2021-03-26 18:11:10 +01:00
38e1c8c5a1 Merge branch 'feature/112-org_registration_links' of git.odit.services:lfk/frontend into feature/112-org_registration_links 2021-03-26 18:04:08 +01:00
c2d29ff233 Added check if key exists
ref #112
2021-03-26 18:04:05 +01:00
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
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
27 changed files with 3650 additions and 2966 deletions

View File

@@ -2,8 +2,94 @@
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.10.0](https://git.odit.services/lfk/frontend/compare/0.9.1...0.10.0)
- Added translations [`79c447b`](https://git.odit.services/lfk/frontend/commit/79c447b4c65e55ebb5af71fb0b09174c36e2cecf)
- Sorted translations 🌍 [`5bd3a46`](https://git.odit.services/lfk/frontend/commit/5bd3a463f00abaf2c98ab554f88e5542d01f364a)
- Reset can now only be triggered if pw is strong enoug [`75d8f73`](https://git.odit.services/lfk/frontend/commit/75d8f7331b6ae78f3979bb62148188a16f83cb8d)
- Module now exports functions that check if a password is strong enough and equal to a potential confirmation field [`b2509e9`](https://git.odit.services/lfk/frontend/commit/b2509e9e53ab6b51dfd55e26712e8928160cd64b)
- Added more cirteria to the password strength component [`5fa9939`](https://git.odit.services/lfk/frontend/commit/5fa9939696a35d60d762feb0cebef61d31869218)
- Now using pw strength component [`6aaf838`](https://git.odit.services/lfk/frontend/commit/6aaf8384512185a3a319ce6b3e2505e910468e64)
- Added a password strength verification [`ad3bd31`](https://git.odit.services/lfk/frontend/commit/ad3bd312e9a5785f81029ea2b7e302ea1addd988)
- Implemented a custom password strength component [`4956bb0`](https://git.odit.services/lfk/frontend/commit/4956bb0e9c3c1d22d60e849aea5664e35330f897)
- User creation can now only be triggered if pw is strong enoug [`540304f`](https://git.odit.services/lfk/frontend/commit/540304f947f60a7072c592ca8088996ce7e95cb4)
- Now using pw strength component for user creation [`7862f44`](https://git.odit.services/lfk/frontend/commit/7862f446532903f1a2eac7b21d5c80c3245785e5)
- Added missing exports [`962dd0c`](https://git.odit.services/lfk/frontend/commit/962dd0c1bbc0df7f20bcec5b4247188c8935c87e)
- new license file version [CI SKIP] [`aedb8a7`](https://git.odit.services/lfk/frontend/commit/aedb8a765ba053545adbba9eb014b3bb0e5aac8c)
- Bumped lfk-client version 🔝 [`cf58bd1`](https://git.odit.services/lfk/frontend/commit/cf58bd15c3541c417ab2be83d96135e931a2b6f6)
- new license file version [CI SKIP] [`34f4f68`](https://git.odit.services/lfk/frontend/commit/34f4f68524918fd3d1963966a1e259d5b60efaca)
- Merge pull request 'Implemented password strength test feature/106-password_strength' (#115) from feature/106-password_strength into dev [`09b8144`](https://git.odit.services/lfk/frontend/commit/09b81440804cf98303fcb723a9717d6d0f432da8)
- Formatting🛠 [`4167819`](https://git.odit.services/lfk/frontend/commit/4167819e7a864d3b1dd95ba48ab1525a454f7f30)
- Now using pw strength component for reset [`5d5f7c7`](https://git.odit.services/lfk/frontend/commit/5d5f7c7f5c6a69146f41996f4facfeff95791be0)
#### [0.9.1](https://git.odit.services/lfk/frontend/compare/0.9.0...0.9.1)
> 26 March 2021
- 🚀RELEASE v0.9.1 [`2ca63fd`](https://git.odit.services/lfk/frontend/commit/2ca63fd1f675f0da2b18ba43095074dd4823991d)
- Merge pull request 'Org selfservice Link feature/112-org_registration_links' (#114) from feature/112-org_registration_links into dev [`a5d25e7`](https://git.odit.services/lfk/frontend/commit/a5d25e7d92c7c37e90dbb4ba74b787873f920c6b)
- Added checkbox to enable registration [`f9fe793`](https://git.odit.services/lfk/frontend/commit/f9fe79357317653b46c09eb95b0db13845cddcf9)
- Sorted translations [`c074c12`](https://git.odit.services/lfk/frontend/commit/c074c12be75f285612f7a732c106404d9fb4538a)
- You can now copy the selfservice links to your clipboard [`fcd55f8`](https://git.odit.services/lfk/frontend/commit/fcd55f89d72e6ceb9bb2bdd194cc3420145d6d0d)
- Formatting [`f185d55`](https://git.odit.services/lfk/frontend/commit/f185d559c0d6476f2f2b9ea74aaad3297411801d)
- Copy now 100% worX [`a3921b4`](https://git.odit.services/lfk/frontend/commit/a3921b45c70b410293db593a75d2fdd34c131733)
- Fixed changes in wrong file [`73d95bc`](https://git.odit.services/lfk/frontend/commit/73d95bc0042f8f586ba2f2345342e25da1d280c2)
- Added check if key exists [`c2d29ff`](https://git.odit.services/lfk/frontend/commit/c2d29ff233f6b3e9dd2555b7e0b845592da2ba35)
- Added check if key exists [`2316baa`](https://git.odit.services/lfk/frontend/commit/2316baa8984832382be9f3b4549ca62cf9ccb5a3)
- Added translations [`ddbc293`](https://git.odit.services/lfk/frontend/commit/ddbc293e9ca0525910bf3d995de970ee2c35c56a)
- new license file version [CI SKIP] [`ded9b58`](https://git.odit.services/lfk/frontend/commit/ded9b589fe087915176c5b54f3c55e412541bc8f)
- Merge pull request 'first merge to main 🚀' (#71) from dev into main [`9aa8e7e`](https://git.odit.services/lfk/frontend/commit/9aa8e7edffa7e51b00a5ab7a8f16828b7a469181)
#### [0.9.0](https://git.odit.services/lfk/frontend/compare/0.8.7...0.9.0)
> 26 March 2021
- 🚀RELEASE v0.9.0 [`67c3732`](https://git.odit.services/lfk/frontend/commit/67c3732fad5a7c64ae11dcbebaaa095e1a2b387c)
- 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.10.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.10.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,8 @@
}, },
"license": "CC-BY-NC-SA-4.0", "license": "CC-BY-NC-SA-4.0",
"dependencies": { "dependencies": {
"@odit/lfk-client-js": "0.7.0", "@odit/lfk-client-js": "0.8.0",
"check-password-strength": "^2.0.2",
"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,224 +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 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"; 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="/cards/*"> <Route path="/cards/*">
<Route path="/"> <Route path="/">
<Cards /> <Cards />
</Route> </Route>
<!-- <Route path="/:scanid" let:params> <!-- <Route path="/:scanid" let:params>
<ScanDetail {params} /> <ScanDetail {params} />
</Route> --> </Route> -->
</Route> </Route>
<Route path="/scans/*"> <Route path="/scans/*">
<Route path="/"> <Route path="/">
<Scans /> <Scans />
</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="/scanstations/*">
<Route path="/"> <Route path="/">
<ScanStations /> <ScanStations />
</Route> </Route>
<Route path="/:stationid" let:params> <Route path="/:stationid" let:params>
<ScanStationDetail {params} /> <ScanStationDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/about"> <Route path="/about">
<About /> <About />
</Route> </Route>
<Route path="/settings"> <Route path="/settings">
<Settings /> <Settings />
</Route> </Route>
</Transition> </Transition>
<Footer /> <Footer />
</Dashboard> </Dashboard>
{:else} {:else}
<Login /> <Login />
{/if} {/if}
</Route> </Route>

View File

@@ -0,0 +1,52 @@
<script context="module">
import { passwordStrength } from "check-password-strength";
export function password_strong_enough(password_change) {
let strength = passwordStrength(password_change);
return (
strength?.contains.includes("lowercase") &&
strength?.contains.includes("uppercase") &&
strength?.contains.includes("number") &&
strength?.length > 9
);
}
export function password_strong_enough_and_equal(
password_change,
password_confirm
) {
return (
password_strong_enough(password_change) &&
password_change === password_confirm
);
}
</script>
<script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { passwordStrength as Strength } from "check-password-strength";
export let password_change;
export let password_confirm;
$: strength = Strength(password_change);
$: passwords_match =
!password_confirm || password_confirm === password_change;
</script>
<div class="ml-4">
<ul class="list-disc font-medium tracking-wide text-red-500 text-xs">
{#if !strength.contains.includes('lowercase')}
<li>{$_('must-contain-a-lowercase-letter')}</li>
{/if}
{#if !strength.contains.includes('uppercase')}
<li>{$_('must-contain-a-uppercase-letter')}</li>
{/if}
{#if !strength.contains.includes('number')}
<li>{$_('must-contain-a-number')}</li>
{/if}
{#if !(strength.length > 9)}
<li>{$_('must-be-at-least-10-characters-long')}</li>
{/if}
{#if !(passwords_match == true)}
<li>{$_('passwords-dont-match')}</li>
{/if}
</ul>
</div>

View File

@@ -1,38 +1,43 @@
<script> <script>
import { AuthService } from "@odit/lfk-client-js"; import { AuthService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import "toastify-js/src/toastify.css"; import "toastify-js/src/toastify.css";
import PasswordStrength, {
password_strong_enough,
} from "../auth/PasswordStrength.svelte";
let state = "reset_in_progress"; let state = "reset_in_progress";
let password = ""; let password = "";
export let params; export let params;
function set_new_password() { function set_new_password() {
if(password.trim() !== ""){ if (password.trim() !== "") {
Toastify({ Toastify({
text: $_('password-reset-in-progress'), text: $_("password-reset-in-progress"),
duration: 3500, duration: 3500,
}).showToast(); }).showToast();
AuthService.authControllerResetPassword(atob(params.resetkey),{ password }) AuthService.authControllerResetPassword(atob(params.resetkey), {
password,
})
.then((resp) => { .then((resp) => {
Toastify({ Toastify({
text: $_('password-reset-successful'), text: $_("password-reset-successful"),
duration: 3500, duration: 3500,
}).showToast(); }).showToast();
state="reset_success"; state = "reset_success";
}) })
.catch((err) => { .catch((err) => {
state="reset_error"; state = "reset_error";
}); });
} else { } else {
Toastify({ Toastify({
text: $_('please-provide-a-password'), text: $_("please-provide-a-password"),
duration: 3500, duration: 3500,
}).showToast(); }).showToast();
} }
} }
</script> </script>
{#if state==="reset_success"} {#if state === 'reset_success'}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
@@ -56,31 +61,31 @@
</div> </div>
</div> </div>
</div> </div>
{:else if state==="reset_error"} {:else if state === 'reset_error'}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_('application_name')}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold"> <p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_('password-reset-failed')} {$_('password-reset-failed')}
</p> </p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900"> <p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('please-request-a-new-reset-mail')} {$_('please-request-a-new-reset-mail')}
</p> </p>
<div class="mt-6">
<div class="mt-6"> <div class="mt-6">
<a <div class="mt-6">
href="/forgot_password/" <a
class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"> href="/forgot_password/"
{$_('request-a-new-reset-mail')} class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
</a> {$_('request-a-new-reset-mail')}
</a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> {:else if state === 'reset_in_progress'}
{:else if state==="reset_in_progress"}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" /> <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
@@ -102,11 +107,14 @@
placeholder={$_('new-password')} placeholder={$_('new-password')}
bind:value={password} /> bind:value={password} />
</div> </div>
<PasswordStrength bind:password_change={password} />
</div> </div>
<div class="mt-5"> <div class="mt-5">
<button <button
on:click={set_new_password} on:click={set_new_password}
disabled={!password_strong_enough(password)}
class:opacity-50={!password_strong_enough(password)}
type="submit" type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm"> class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
<span class="absolute left-0 inset-y pl-3"> <span class="absolute left-0 inset-y pl-3">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -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

@@ -11,6 +11,8 @@
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 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) ||
@@ -19,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 = {};
@@ -28,7 +33,9 @@
$: 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_show = true; $: sponsoring_contracts_show = true;
$: cards_show = true;
$: generate_orgs = [original_object]; $: 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(
@@ -96,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({
@@ -108,9 +116,46 @@
} else { } else {
} }
} }
async function copy() {
if(!editable.registrationKey){
Toastify({
text: $_('you-have-to-save-your-changes-to-generate-a-link'),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
return;
}
valueCopy = registrationLink;
await tick();
areaDom.focus();
areaDom.select();
try {
const successful = document.execCommand("copy");
if (!successful) {
throw new Error();
}
Toastify({
text: $_("copied-link-to-clipboard"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
copied = true;
} catch (err) {
Toastify({
text: $_("error-whyile-copying-to-clipboard"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
}
// we can notifi by event or storage about copy status
valueCopy = null;
}
export let import_modal_open = false; 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;
@@ -130,6 +175,7 @@
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_orgs /> bind:generate_orgs />
<GenerateRunnerCards bind:cards_show bind:generate_orgs />
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button <button
on:click={() => { on:click={() => {
@@ -272,99 +318,151 @@
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="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>
{#if editable.registrationKey}
<p class="text-gray-500 text-xs">
{$_('click-to-copy-the-link-into-your-clipboard')}
</p>
{/if}
</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,207 +1,212 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let modal_open = false; let modal_open = false;
let delete_org = {}; let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte"; import OrgsEmptyState from "./OrgsEmptyState.svelte";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte"; import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
$: searchvalue = ""; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: active_deletes = []; $: searchvalue = "";
$: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true); $: active_deletes = [];
$: generate_orgs = current_organizations.filter((r) => r.is_selected === true); $: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true);
export let current_organizations = []; $: cards_show = current_organizations.some((r) => r.is_selected === true);
$: generate_orgs = current_organizations.filter((r) => r.is_selected === true);
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then( export let current_organizations = [];
(val) => {
current_organizations = val; const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
} (val) => {
); current_organizations = val;
</script> }
);
<ConfirmOrgDeletion </script>
on:cancelDelete={(event) => {
modal_open = false; <ConfirmOrgDeletion
active_deletes[event.detail.id] = false; on:cancelDelete={(event) => {
}} modal_open = false;
bind:modal_open active_deletes[event.detail.id] = false;
bind:delete_org /> }}
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} bind:modal_open
{#await promise} bind:delete_org />
<div {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" {#await promise}
role="alert"> <div
<p class="font-bold">{$_('organizations-are-being-loaded')}</p> class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
<p class="text-sm">{$_('this-might-take-a-moment')}</p> role="alert">
</div> <p class="font-bold">{$_('organizations-are-being-loaded')}</p>
{:then} <p class="text-sm">{$_('this-might-take-a-moment')}</p>
{#if current_organizations.length === 0} </div>
<OrgsEmptyState /> {:then}
{:else} {#if current_organizations.length === 0}
<input <OrgsEmptyState />
type="search" {:else}
bind:value={searchvalue} <input
placeholder={$_('datatable.search')} type="search"
aria-label={$_('datatable.search')} bind:value={searchvalue}
class="gridjs-input gridjs-search-input mb-4" /> placeholder={$_('datatable.search')}
<div class="h-12"> aria-label={$_('datatable.search')}
<GenerateSponsoringContracts class="gridjs-input gridjs-search-input mb-4" />
bind:sponsoring_contracts_show <div class="h-12">
bind:generate_orgs /> <GenerateSponsoringContracts
</div> bind:sponsoring_contracts_show
<div bind:generate_orgs />
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> <GenerateRunnerCards
<table class="divide-y divide-gray-200 w-full"> bind:cards_show
<thead class="bg-gray-50"> bind:generate_orgs />
<tr> </div>
<th <div
scope="col" class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <table class="divide-y divide-gray-200 w-full">
<span <thead class="bg-gray-50">
on:click={() => { <tr>
const newstate = !current_organizations.some((r) => r.is_selected === true); <th
current_organizations = current_organizations.map((r) => { scope="col"
r.is_selected = newstate; class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
return r; <span
}); on:click={() => {
}} const newstate = !current_organizations.some((r) => r.is_selected === true);
class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)} current_organizations = current_organizations.map((r) => {
{$_('deselect-all')} r.is_selected = newstate;
{:else}{$_('select-all')}{/if} return r;
</span> });
</th> }}
<th class="underline cursor-pointer select-none">{#if current_organizations.some((r) => r.is_selected === true)}
scope="col" {$_('deselect-all')}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> {:else}{$_('select-all')}{/if}
{$_('name')} </span>
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('address')} {$_('name')}
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact')} {$_('address')}
</th> </th>
<th scope="col" class="relative px-6 py-3"> <th
<span class="sr-only">{$_('action')}</span> scope="col"
</th> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</tr> {$_('contact')}
</thead> </th>
<tbody class="divide-y divide-gray-200"> <th scope="col" class="relative px-6 py-3">
{#each current_organizations as o} <span class="sr-only">{$_('action')}</span>
{#if Object.values(o) </th>
.toString() </tr>
.toLowerCase() </thead>
.includes(searchvalue)} <tbody class="divide-y divide-gray-200">
<tr data-rowid="org_{o.id}"> {#each current_organizations as o}
<td class="px-6 py-4 whitespace-nowrap"> {#if Object.values(o)
<input .toString()
bind:checked={o.is_selected} .toLowerCase()
type="checkbox" .includes(searchvalue)}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> <tr data-rowid="org_{o.id}">
</td> <td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-4 whitespace-nowrap"> <input
<div class="flex items-center"> bind:checked={o.is_selected}
<div class="ml-4"> type="checkbox"
<div class="text-sm font-medium text-gray-900"> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
{o.name} </td>
</div> <td class="px-6 py-4 whitespace-nowrap">
</div> <div class="flex items-center">
</div> <div class="ml-4">
</td> <div class="text-sm font-medium text-gray-900">
<td class="px-6 py-4 whitespace-nowrap"> {o.name}
<div class="flex items-center"> </div>
<div class="ml-4"> </div>
<div class="text-sm font-medium text-gray-900"> </div>
{#if o.address.address1 !== null} </td>
{o.address.address1}<br /> <td class="px-6 py-4 whitespace-nowrap">
{o.address.address2 || ''}<br /> <div class="flex items-center">
{o.address.postalcode} <div class="ml-4">
{o.address.city} <div class="text-sm font-medium text-gray-900">
{o.address.country} {#if o.address.address1 !== null}
{/if} {o.address.address1}<br />
</div> {o.address.address2 || ''}<br />
</div> {o.address.postalcode}
</div> {o.address.city}
</td> {o.address.country}
<td class="px-6 py-4 whitespace-nowrap"> {/if}
<div class="flex items-center"> </div>
<div class="ml-4"> </div>
<div class="text-sm font-medium text-gray-900"> </div>
{#if o.contact} </td>
<a <td class="px-6 py-4 whitespace-nowrap">
href="../contacts/{o.contact.id}" <div class="flex items-center">
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname} <div class="ml-4">
{o.contact.middlename || ''} <div class="text-sm font-medium text-gray-900">
{o.contact.lastname}</a> {#if o.contact}
{:else}{$_('no-contact-specified')}{/if} <a
</div> href="../contacts/{o.contact.id}"
</div> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
</div> {o.contact.middlename || ''}
</td> {o.contact.lastname}</a>
{#if active_deletes[o.id] === true} {:else}{$_('no-contact-specified')}{/if}
<td </div>
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> </div>
<button </div>
on:click={() => { </td>
active_deletes[o.id] = false; {#if active_deletes[o.id] === true}
}} <td
tabindex="0" class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> <button
<button on:click={() => {
on:click={() => { active_deletes[o.id] = false;
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false) }}
.then((resp) => { tabindex="0"
current_organizations = current_organizations.filter((obj) => obj.id !== o.id); class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
Toastify({ <button
text: 'Organization deleted', on:click={() => {
duration: 500, RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
backgroundColor: .then((resp) => {
'linear-gradient(to right, #00b09b, #96c93d)', current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
}).showToast(); Toastify({
}) text: 'Organization deleted',
.catch((err) => { duration: 500,
modal_open = true; backgroundColor:
delete_org = o; 'linear-gradient(to right, #00b09b, #96c93d)',
}); }).showToast();
}} })
tabindex="0" .catch((err) => {
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> modal_open = true;
</td> delete_org = o;
{:else} });
<td }}
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> tabindex="0"
<a class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
href="./{o.id}" </td>
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> {:else}
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')} <td
<button class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
on:click={() => { <a
active_deletes[o.id] = true; href="./{o.id}"
}} class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
tabindex="0" {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> <button
{/if} on:click={() => {
</td> active_deletes[o.id] = true;
{/if} }}
</tr> tabindex="0"
{/if} class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/each} {/if}
</tbody> </td>
</table> {/if}
</div> </tr>
{/if} {/if}
{:catch error} {/each}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> </tbody>
<span class="inline-block align-middle mr-8"> </table>
<b class="capitalize">{$_('general_promise_error')}</b> </div>
{error} {/if}
</span> {:catch error}
</div> <div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
{/await} <span class="inline-block align-middle mr-8">
{/if} <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

@@ -1,252 +1,254 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { RunnerOrganizationService, RunnerTeamService } from "@odit/lfk-client-js"; import {
import Toastify from "toastify-js"; RunnerOrganizationService,
export let sponsoring_contracts_show = false; RunnerTeamService,
export let generate_runners = []; } from "@odit/lfk-client-js";
export let generate_orgs = []; import Toastify from "toastify-js";
export let generate_teams = []; export let sponsoring_contracts_show = false;
$: sponsoring_contracts_download_open = false; export let generate_runners = [];
document.addEventListener("click", function (e) { export let generate_orgs = [];
if ( export let generate_teams = [];
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" && $: sponsoring_contracts_download_open = false;
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu" document.addEventListener("click", function (e) {
) { if (
sponsoring_contracts_download_open = false; 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) { function generateSponsoringContract(locale) {
generateOrgContracts(locale); sponsoring_contracts_download_open = false;
} else if (generate_teams.length > 0) {
generateTeamContracts(locale); if (generate_orgs.length > 0) {
} else { generateOrgContracts(locale);
generateRunnerContracts(locale); } else if (generate_teams.length > 0) {
} generateTeamContracts(locale);
} } else {
generateRunnerContracts(locale);
async function generateTeamContracts(locale) { }
const toast = Toastify({ }
text: $_("generating-pdfs"),
duration: -1, async function generateTeamContracts(locale) {
}).showToast(); const toast = Toastify({
let count = 0; text: $_("generating-pdfs"),
for await (const t of generate_teams) { duration: -1,
count++; }).showToast();
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( let count = 0;
t.id for await (const t of generate_teams) {
); count++;
fetch( const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, t.id
{ );
method: "POST", fetch(
headers: { `${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
"Content-Type": "application/json", {
}, method: "POST",
body: JSON.stringify(runners), headers: {
} "Content-Type": "application/json",
) },
.then((response) => { body: JSON.stringify(runners),
if (response.status != "200") { }
toast.hideToast(); )
Toastify({ .then((response) => {
text: $_("pdf-generation-failed"), if (response.status != "200") {
duration: 3500, toast.hideToast();
backgroundColor: Toastify({
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", text: $_("pdf-generation-failed"),
}).showToast(); duration: 3500,
} else { backgroundColor:
return response.blob(); "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
} }).showToast();
}) } else {
.then((blob) => { return response.blob();
const url = window.URL.createObjectURL(blob); }
let a = document.createElement("a"); })
a.href = url; .then((blob) => {
a.download = "Sponsorings_" + t.name + ".pdf"; const url = window.URL.createObjectURL(blob);
document.body.appendChild(a); let a = document.createElement("a");
a.click(); a.href = url;
a.remove(); a.download = "Sponsorings_" + t.name + ".pdf";
if (count === generate_teams.length) { document.body.appendChild(a);
toast.hideToast(); a.click();
Toastify({ a.remove();
text: $_("pdfs-successfully-generated"), if (count === generate_teams.length) {
duration: 3500, toast.hideToast();
backgroundColor: Toastify({
"linear-gradient(to right, #00b09b, #96c93d)", text: $_("pdfs-successfully-generated"),
}).showToast(); duration: 3500,
} backgroundColor:
}) "linear-gradient(to right, #00b09b, #96c93d)",
.catch((err) => {}); }).showToast();
} }
} })
.catch((err) => {});
async function generateOrgContracts(locale) { }
const toast = Toastify({ }
text: $_("generating-pdf"),
duration: -1, async function generateOrgContracts(locale) {
}).showToast(); const toast = Toastify({
let count = 0; text: $_("generating-pdf"),
for await (const o of generate_orgs) { duration: -1,
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners( }).showToast();
o.id for await (const o of generate_orgs) {
); const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
fetch( o.id
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, );
{ fetch(
method: "POST", `${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
headers: { {
"Content-Type": "application/json", method: "POST",
}, headers: {
body: JSON.stringify(runners), "Content-Type": "application/json",
} },
) body: JSON.stringify(runners),
.then((response) => { }
if (response.status != "200") { )
toast.hideToast(); .then((response) => {
Toastify({ if (response.status != "200") {
text: $_("pdf-generation-failed"), toast.hideToast();
duration: 3500, Toastify({
backgroundColor: text: $_("pdf-generation-failed"),
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", duration: 3500,
}).showToast(); backgroundColor:
} else { "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
return response.blob(); }).showToast();
} } else {
}) return response.blob();
.then((blob) => { }
count++; })
const url = window.URL.createObjectURL(blob); .then((blob) => {
let a = document.createElement("a"); count++;
a.href = url; const url = window.URL.createObjectURL(blob);
a.download = "Sponsorings_" + o.name + ".pdf"; let a = document.createElement("a");
document.body.appendChild(a); a.href = url;
a.click(); a.download = "Sponsorings_" + o.name + ".pdf";
a.remove(); document.body.appendChild(a);
if (count === generate_orgs.length) { a.click();
toast.hideToast(); a.remove();
Toastify({ if (count === generate_orgs.length) {
text: $_("pdfs-successfully-generated"), toast.hideToast();
duration: 3500, Toastify({
backgroundColor: text: $_("pdfs-successfully-generated"),
"linear-gradient(to right, #00b09b, #96c93d)", duration: 3500,
}).showToast(); backgroundColor:
} "linear-gradient(to right, #00b09b, #96c93d)",
}) }).showToast();
.catch((err) => {}); }
} })
} .catch((err) => {});
}
function generateRunnerContracts(locale) { }
const toast = Toastify({
text: $_("generating-pdf"), function generateRunnerContracts(locale) {
duration: -1, const toast = Toastify({
}).showToast(); text: $_("generating-pdf"),
fetch( duration: -1,
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, }).showToast();
{ fetch(
method: "POST", `${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
headers: { {
"Content-Type": "application/json", method: "POST",
}, headers: {
body: JSON.stringify(generate_runners), "Content-Type": "application/json",
} },
) body: JSON.stringify(generate_runners),
.then((response) => { }
if (response.status != "200") { )
toast.hideToast(); .then((response) => {
Toastify({ if (response.status != "200") {
text: $_("pdf-generation-failed"), toast.hideToast();
duration: 3500, Toastify({
backgroundColor: text: $_("pdf-generation-failed"),
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", duration: 3500,
}).showToast(); backgroundColor:
} else { "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
return response.blob(); }).showToast();
} } else {
}) return response.blob();
.then((blob) => { }
const url = window.URL.createObjectURL(blob); })
let a = document.createElement("a"); .then((blob) => {
a.href = url; const url = window.URL.createObjectURL(blob);
a.download = "Sponsoring.pdf"; let a = document.createElement("a");
document.body.appendChild(a); a.href = url;
a.click(); a.download = "Sponsoring.pdf";
a.remove(); document.body.appendChild(a);
toast.hideToast(); a.click();
Toastify({ a.remove();
text: $_("pdf-successfully-generated"), toast.hideToast();
duration: 3500, Toastify({
backgroundColor: text: $_("pdf-successfully-generated"),
"linear-gradient(to right, #00b09b, #96c93d)", duration: 3500,
}).showToast(); backgroundColor:
}) "linear-gradient(to right, #00b09b, #96c93d)",
.catch((err) => { }).showToast();
console.error(err); })
}); .catch((err) => {
} console.error(err);
</script> });
}
{#if sponsoring_contracts_show} </script>
<div id="sponsoring:dropdown" class="relative inline-block">
<div> {#if sponsoring_contracts_show}
<button <div id="sponsoring:dropdown" class="relative inline-block">
on:click={() => { <div>
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; <button
}} on:click={() => {
type="button" sponsoring_contracts_download_open = !sponsoring_contracts_download_open;
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" type="button"
aria-haspopup="true" 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"
aria-expanded="true"> id="options-menu"
{$_('generate-sponsoring-contracts')} aria-haspopup="true"
<svg aria-expanded="true">
xmlns="http://www.w3.org/2000/svg" {$_('generate-sponsoring-contracts')}
width="24" <svg
height="24" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24"
class="-mr-1 ml-2 h-5 w-5"><path height="24"
fill="none" viewBox="0 0 24 24"
d="M0 0h24v24H0z" /> class="-mr-1 ml-2 h-5 w-5"><path
<path fill="none"
fill="currentColor" d="M0 0h24v24H0z" />
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> <path
</button> fill="currentColor"
</div> 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>
{#if sponsoring_contracts_download_open} </button>
<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" {#if sponsoring_contracts_download_open}
id="sponsoring:dropdown:menu"> <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"
class="py-1" id="sponsoring:dropdown:menu">
role="menu" <div
aria-orientation="vertical" class="py-1"
aria-labelledby="options-menu"> role="menu"
<span aria-orientation="vertical"
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span> aria-labelledby="options-menu">
<button <span
on:click={() => { class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
generateSponsoringContract('de'); <button
}} on:click={() => {
type="submit" generateSponsoringContract('de');
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"> type="submit"
{$_('german')} 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"
</button> role="menuitem">
<button {$_('german')}
on:click={() => { </button>
generateSponsoringContract('en'); <button
}} on:click={() => {
type="submit" generateSponsoringContract('en');
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"> type="submit"
{$_('english')} 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"
</button> role="menuitem">
</div> {$_('english')}
</div> </button>
{/if} </div>
</div> </div>
{/if} {/if}
</div>
{/if}

View File

@@ -1,293 +1,297 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelteContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import store from "../../store"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import { import store from "../../store";
RunnerService, import {
RunnerTeamService, RunnerService,
RunnerOrganizationService, RunnerTeamService,
} from "@odit/lfk-client-js"; RunnerOrganizationService,
import Toastify from "toastify-js"; } from "@odit/lfk-client-js";
import PromiseError from "../base/PromiseError.svelte"; import Toastify from "toastify-js";
import isEmail from "validator/es/lib/isEmail"; import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select"; import isEmail from "validator/es/lib/isEmail";
let data_loaded = false; import Select from "svelte-select";
export let params; let data_loaded = false;
const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid); export let params;
$: delete_triggered = false; const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid);
$: sponsoring_contracts_download_open = false; $: delete_triggered = false;
$: original_data_pdf = {}; $: original_data_pdf = {};
$: original_data = {}; $: original_data = {};
$: editable = {}; $: editable = {};
$: group = {}; $: group = {};
$: changes_performed = !( $: changes_performed = !(
JSON.stringify(original_data) == JSON.stringify(editable) JSON.stringify(original_data) == JSON.stringify(editable)
); );
$: isEmailValid = $: isEmailValid =
(editable.email || "") === "" || (editable.email || "") === "" ||
(editable.email && isEmail(editable.email || "")); (editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== ""; $: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== ""; $: isLastnameValid = editable.lastname !== "";
$: save_enabled = $: save_enabled =
changes_performed && changes_performed &&
isFirstnameValid && isFirstnameValid &&
isLastnameValid && isLastnameValid &&
isEmailValid && isEmailValid &&
editable.group != null; editable.group != null;
$: sponsoring_contracts_show = true; $: sponsoring_contracts_show = true;
$: generate_runners = [original_data_pdf]; $: cards_show = true;
runner_promise.then((data) => { $: generate_runners = [original_data_pdf];
data_loaded = true; runner_promise.then((data) => {
original_data_pdf = Object.assign(original_data_pdf, data); data_loaded = true;
data.group = data.group.id; original_data_pdf = Object.assign(original_data_pdf, data);
original_data = Object.assign(original_data, data); data.group = data.group.id;
editable = Object.assign(editable, original_data); original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => { RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
const orgs = val.map((r) => { (val) => {
return { label: r.name, value: r }; const orgs = val.map((r) => {
}); return { label: r.name, value: r };
groups = groups.concat(orgs); });
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { groups = groups.concat(orgs);
const teams = val.map((r) => { RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
return { label: `${r.parentGroup.name} > ${r.name}`, value: r }; const teams = val.map((r) => {
}); return { label: `${r.parentGroup.name} > ${r.name}`, value: r };
groups = groups.concat(teams); });
group = groups.find((g) => g.value.id == editable.group); groups = groups.concat(teams);
}); group = groups.find((g) => g.value.id == editable.group);
} });
); }
}); );
let groups = []; });
function submit() { let groups = [];
if (data_loaded === true && save_enabled) { function submit() {
Toastify({ if (data_loaded === true && save_enabled) {
text: $_("updating-runner"), Toastify({
duration: 2500, text: $_("updating-runner"),
}).showToast(); duration: 2500,
let postdata = {}; }).showToast();
postdata = Object.assign(postdata, editable); let postdata = {};
RunnerService.runnerControllerPut(original_data.id, postdata) postdata = Object.assign(postdata, editable);
.then((resp) => { RunnerService.runnerControllerPut(original_data.id, postdata)
Object.assign(original_data, editable); .then((resp) => {
original_data = original_data; Object.assign(original_data, editable);
Toastify({ original_data = original_data;
text: $_("runner-updated"), Toastify({
duration: 2500, text: $_("runner-updated"),
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", duration: 2500,
}).showToast(); backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}) }).showToast();
.catch((err) => {}); })
} else { .catch((err) => {});
} } else {
} }
function deleteRunner() { }
RunnerService.runnerControllerRemove(original_data.id, true) function deleteRunner() {
.then((resp) => { RunnerService.runnerControllerRemove(original_data.id, true)
location.replace("./"); .then((resp) => {
}) location.replace("./");
.catch((err) => {}); })
} .catch((err) => {});
</script> }
</script>
{#await runner_promise}
{$_('loading-runners')} {#await runner_promise}
{:then} {$_('loading-runners')}
<section class="container p-5 select-none"> {:then}
<div class="flex flex-row mb-4"> <section class="container p-5 select-none">
<div class="w-full"> <div class="flex flex-row mb-4">
<nav class="w-full flex"> <div class="w-full">
<ol class="list-none flex flex-row items-center justify-start"> <nav class="w-full flex">
<li class="flex items-center"> <ol class="list-none flex flex-row items-center justify-start">
<svg <li class="flex items-center">
xmlns="http://www.w3.org/2000/svg" <svg
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
class="flex-shrink-0 w-5 h-5 mr-2" viewBox="0 0 24 24"
fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2"
width="24" fill="currentColor"
height="24"><path fill="none" d="M0 0h24v24H0z" /> width="24"
<path height="24"><path fill="none" d="M0 0h24v24H0z" />
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> <path
</li> 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>
<li class="flex items-center"> </li>
<a class="mr-2" href="./">{$_('runners')}</a><svg <li class="flex items-center">
stroke="currentColor" <a class="mr-2" href="./">{$_('runners')}</a><svg
fill="none" stroke="currentColor"
stroke-width="2" fill="none"
viewBox="0 0 24 24" stroke-width="2"
stroke-linecap="round" viewBox="0 0 24 24"
stroke-linejoin="round" stroke-linecap="round"
class="h-3 w-3 mr-2 stroke-current" stroke-linejoin="round"
height="1em" class="h-3 w-3 mr-2 stroke-current"
width="1em" height="1em"
xmlns="http://www.w3.org/2000/svg"><line width="1em"
x1="5" xmlns="http://www.w3.org/2000/svg"><line
y1="12" x1="5"
x2="19" y1="12"
y2="12" /> x2="19"
<polyline points="12 5 19 12 12 19" /></svg> y2="12" />
</li> <polyline points="12 5 19 12 12 19" /></svg>
<li class="flex items-center"> </li>
<span class="mr-2">{original_data.firstname} <li class="flex items-center">
{original_data.middlename || ''} <span class="mr-2">{original_data.firstname}
{original_data.lastname}</span> {original_data.middlename || ''}
</li> {original_data.lastname}</span>
</ol> </li>
</nav> </ol>
</div> </nav>
</div> </div>
<div class="mb-8 text-3xl font-extrabold leading-tight"> </div>
{original_data.firstname} <div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.middlename || ''} {original_data.firstname}
{original_data.lastname} {original_data.middlename || ''}
<span data-id="runner_actions_${editable.id}"> {original_data.lastname}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} <span data-id="runner_actions_${editable.id}">
{#if delete_triggered} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
<button {#if delete_triggered}
on:click={deleteRunner} <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">{$_('confirm-deletion')}</button> on:click={deleteRunner}
<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">{$_('confirm-deletion')}</button>
on:click={() => { <button
delete_triggered = !delete_triggered; on:click={() => {
}} delete_triggered = !delete_triggered;
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> }}
{/if} 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>
<GenerateSponsoringContracts {/if}
bind:sponsoring_contracts_show <GenerateSponsoringContracts
bind:generate_runners /> bind:sponsoring_contracts_show
{#if !delete_triggered} bind:generate_runners />
<button <GenerateRunnerCards
on:click={() => { bind:sponsoring_contracts_show
delete_triggered = true; bind:generate_runners />
}} {#if !delete_triggered}
type="button" <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-runner')}</button> on:click={() => {
{/if} delete_triggered = true;
{/if} }}
{#if !delete_triggered} type="button"
<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-runner')}</button>
disabled={!save_enabled} {/if}
class:opacity-50={!save_enabled} {/if}
type="button" {#if !delete_triggered}
on:click={submit} <button
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> disabled={!save_enabled}
{/if} class:opacity-50={!save_enabled}
</span> type="button"
</div> on:click={submit}
<!-- --> 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>
<div class="text-sm w-full"> {/if}
<label </span>
for="firstname" </div>
class="font-medium text-gray-700">{$_('first-name')}</label> <!-- -->
<input <div class="text-sm w-full">
autocomplete="off" <label
placeholder={$_('first-name')} for="firstname"
type="text" class="font-medium text-gray-700">{$_('first-name')}</label>
class:border-red-500={!isFirstnameValid} <input
class:focus:border-red-500={!isFirstnameValid} autocomplete="off"
class:focus:ring-red-500={!isFirstnameValid} placeholder={$_('first-name')}
bind:value={editable.firstname} type="text"
name="firstname" class:border-red-500={!isFirstnameValid}
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:focus:border-red-500={!isFirstnameValid}
{#if !isFirstnameValid} class:focus:ring-red-500={!isFirstnameValid}
<span bind:value={editable.firstname}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> name="firstname"
{$_('first-name-is-required')} 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" />
</span> {#if !isFirstnameValid}
{/if} <span
</div> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
<div class="text-sm w-full"> {$_('first-name-is-required')}
<label </span>
for="middlename" {/if}
class="font-medium text-gray-700">{$_('middle-name')}</label> </div>
<input <div class="text-sm w-full">
autocomplete="off" <label
placeholder={$_('middle-name')} for="middlename"
type="text" class="font-medium text-gray-700">{$_('middle-name')}</label>
bind:value={editable.middlename} <input
name="middlename" autocomplete="off"
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" /> placeholder={$_('middle-name')}
</div> type="text"
<div class="text-sm w-full"> bind:value={editable.middlename}
<label name="middlename"
for="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" />
class="font-medium text-gray-700">{$_('last-name')}</label> </div>
<input <div class="text-sm w-full">
autocomplete="off" <label
placeholder={$_('last-name')} for="lastname"
type="text" class="font-medium text-gray-700">{$_('last-name')}</label>
bind:value={editable.lastname} <input
class:border-red-500={!isLastnameValid} autocomplete="off"
class:focus:border-red-500={!isLastnameValid} placeholder={$_('last-name')}
class:focus:ring-red-500={!isLastnameValid} type="text"
name="lastname" bind:value={editable.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" /> class:border-red-500={!isLastnameValid}
{#if !isLastnameValid} class:focus:border-red-500={!isLastnameValid}
<span class:focus:ring-red-500={!isLastnameValid}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> name="lastname"
{$_('last-name-is-required')} 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" />
</span> {#if !isLastnameValid}
{/if} <span
</div> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
<div class="text-sm w-full"> {$_('last-name-is-required')}
<label </span>
for="email" {/if}
class="font-medium text-gray-700">{$_('e-mail-adress')}</label> </div>
<input <div class="text-sm w-full">
autocomplete="off" <label
placeholder={$_('e-mail-adress')} for="email"
type="email" class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
bind:value={editable.email} <input
class:border-red-500={!isEmailValid} autocomplete="off"
class:focus:border-red-500={!isEmailValid} placeholder={$_('e-mail-adress')}
class:focus:ring-red-500={!isEmailValid} type="email"
name="email" bind:value={editable.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" /> class:border-red-500={!isEmailValid}
{#if !isEmailValid} class:focus:border-red-500={!isEmailValid}
<span class:focus:ring-red-500={!isEmailValid}
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> name="email"
{$_('valid-email-is-required')} 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" />
</span> {#if !isEmailValid}
{/if} <span
</div> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
<div class="text-sm w-full"> {$_('valid-email-is-required')}
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label> </span>
<input {/if}
autocomplete="off" </div>
placeholder={$_('phone')} <div class="text-sm w-full">
type="tel" <label for="phone" class="font-medium text-gray-700">{$_('phone')}</label>
bind:value={editable.phone} <input
name="phone" autocomplete="off"
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" /> placeholder={$_('phone')}
</div> type="tel"
<div class="text-sm w-full"> bind:value={editable.phone}
<span class="font-medium text-gray-700">{$_('group')}</span> name="phone"
<Select 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" />
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" </div>
itemFilter={(label, filterText, option) => label <div class="text-sm w-full">
.toLowerCase() <span class="font-medium text-gray-700">{$_('group')}</span>
.includes( <Select
filterText.toLowerCase() 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"
) || option.id.value itemFilter={(label, filterText, option) => label
.toString() .toLowerCase()
.startsWith(filterText.toLowerCase())} .includes(
items={groups} filterText.toLowerCase()
showChevron={true} ) || option.id.value
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} .toString()
noOptionsMessage={$_('no-organization-or-team-found')} .startsWith(filterText.toLowerCase())}
bind:selectedValue={group} items={groups}
on:select={(selectedValue) => { showChevron={true}
editable.group = selectedValue.detail.value.id; placeholder={$_('search-for-an-organization-or-team-by-name-or-id')}
}} noOptionsMessage={$_('no-organization-or-team-found')}
on:clear={() => (editable.group = null)} /> bind:selectedValue={group}
</div> on:select={(selectedValue) => {
<div class="text-sm w-full"> editable.group = selectedValue.detail.value.id;
<span class="font-medium text-gray-700">{$_('distance')}</span> }}
<br /> on:clear={() => (editable.group = null)} />
<span class="text-gray-700">{original_data.distance} km</span> </div>
</div> <div class="text-sm w-full">
</section> <span class="font-medium text-gray-700">{$_('distance')}</span>
{:catch error} <br />
<PromiseError {error} /> <span class="text-gray-700">{original_data.distance} km</span>
{/await} </div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

@@ -1,249 +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 GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; 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_show = current_runners.some( $: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value);
(r) => r.is_selected === true $: sponsoring_contracts_show = current_runners.some(
); (r) => r.is_selected === true
$: generate_runners = current_runners.filter((r) => r.is_selected === true); );
$: teams = []; $: cards_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 }; $: generate_runners = current_runners.filter((r) => r.is_selected === true);
}); $: teams = [];
$: selectgroups = orgs $: orgs = [];
.map(function (g) { $: mappedteams = teams.map(function (g) {
return { value: g.id, label: g.name }; return { value: g.id, label: g.parentGroup.name + " > " + g.name };
}) });
.concat(mappedteams); $: selectgroups = orgs
.map(function (g) {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { return { value: g.id, label: g.name };
teams = val; })
}); .concat(mappedteams);
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val; RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
}); teams = val;
function should_display_based_on_id(id) { });
if (searchvalue.toString().slice(-1) === "*") { RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
return id.toString().startsWith(searchvalue.replace("*", "")); orgs = val;
} });
return id.toString() === searchvalue; function should_display_based_on_id(id) {
} if (searchvalue.toString().slice(-1) === "*") {
</script> return id.toString().startsWith(searchvalue.replace("*", ""));
}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} return id.toString() === searchvalue;
{#await runners_promise} }
<div </script>
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"> {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
<p class="font-bold">{$_('runners-are-being-loaded')}</p> {#await runners_promise}
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <div
</div> class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
{:then} role="alert">
{#if current_runners.length === 0} <p class="font-bold">{$_('runners-are-being-loaded')}</p>
<RunnersEmptyState /> <p class="text-sm">{$_('this-might-take-a-moment')}</p>
{:else} </div>
<input {:then}
type="search" {#if current_runners.length === 0}
bind:value={searchvalue} <RunnersEmptyState />
placeholder={$_('datatable.search')} {:else}
aria-label={$_('datatable.search')} <input
class="gridjs-input gridjs-search-input mb-4" /> type="search"
<div class="block mb-6"> bind:value={searchvalue}
<label placeholder={$_('datatable.search')}
for="country" aria-label={$_('datatable.search')}
class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label> class="gridjs-input gridjs-search-input mb-4" />
<Select <div class="block mb-6">
on:select={(event) => { <label
selectedFilter = event.detail; for="country"
}} class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label>
selectedValue={selectedFilter} <Select
placeholder={$_('filter-by-organization-team')} on:select={(event) => {
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" selectedFilter = event.detail;
items={selectgroups} }}
isMulti={true} /> selectedValue={selectedFilter}
</div> placeholder={$_('filter-by-organization-team')}
<div class="h-12"> 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"
<GenerateSponsoringContracts items={selectgroups}
bind:sponsoring_contracts_show isMulti={true} />
bind:generate_runners /> </div>
</div> <div class="h-12">
<div <GenerateSponsoringContracts
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> bind:sponsoring_contracts_show
<table class="divide-y divide-gray-200 w-full"> bind:generate_runners />
<thead class="bg-gray-50"> <GenerateRunnerCards
<tr> bind:cards_show
<th bind:generate_runners />
scope="col" </div>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <div
<span class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
on:click={() => { <table class="divide-y divide-gray-200 w-full">
const newstate = !current_runners.some((r) => r.is_selected === true); <thead class="bg-gray-50">
current_runners = current_runners.map((r) => { <tr>
r.is_selected = newstate; <th
return r; scope="col"
}); class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
}} <span
class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)} on:click={() => {
{$_('deselect-all')} const newstate = !current_runners.some((r) => r.is_selected === true);
{:else}{$_('select-all')}{/if} current_runners = current_runners.map((r) => {
</span> r.is_selected = newstate;
</th> return r;
<th });
scope="col" }}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)}
{$_('name')} {$_('deselect-all')}
</th> {:else}{$_('select-all')}{/if}
<th </span>
scope="col" </th>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th
{$_('contact-information')} scope="col"
</th> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th {$_('name')}
scope="col" </th>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th
{$_('group')} scope="col"
</th> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th {$_('contact-information')}
scope="col" </th>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th
{$_('distance-in-km')} scope="col"
</th> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th scope="col" class="relative px-6 py-3"> {$_('group')}
<span class="sr-only">{$_('action')}</span> </th>
</th> <th
</tr> scope="col"
</thead> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<tbody class="divide-y divide-gray-200"> {$_('distance-in-km')}
{#each current_runners as runner} </th>
{#if runner.firstname <th scope="col" class="relative px-6 py-3">
.toLowerCase() <span class="sr-only">{$_('action')}</span>
.includes( </th>
searchvalue.toLowerCase() </tr>
) || runner.lastname </thead>
.toLowerCase() <tbody class="divide-y divide-gray-200">
.includes( {#each current_runners as runner}
searchvalue.toLowerCase() {#if runner.firstname
) || should_display_based_on_id(runner.id)} .toLowerCase()
{#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0} .includes(
<tr searchvalue.toLowerCase()
data-rowid="user_{runner.id}" ) || runner.lastname
data-groupid={runner.group.id}> .toLowerCase()
<td class="px-6 py-4 whitespace-nowrap"> .includes(
<input searchvalue.toLowerCase()
bind:checked={runner.is_selected} ) || should_display_based_on_id(runner.id)}
type="checkbox" {#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> <tr
</td> data-rowid="user_{runner.id}"
<td class="px-6 py-4 whitespace-nowrap"> data-groupid={runner.group.id}>
<div class="flex items-center"> <td class="px-6 py-4 whitespace-nowrap">
<div class="ml-4"> <input
<div class="text-sm font-medium text-gray-900"> bind:checked={runner.is_selected}
{runner.firstname} type="checkbox"
{runner.middlename || ''} class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
{runner.lastname} </td>
</div> <td class="px-6 py-4 whitespace-nowrap">
</div> <div class="flex items-center">
</div> <div class="ml-4">
</td> <div class="text-sm font-medium text-gray-900">
<td class="px-6 py-4 whitespace-nowrap"> {runner.firstname}
{#if runner.email} {runner.middlename || ''}
<div class="text-sm text-gray-500">{runner.email}</div> {runner.lastname}
{/if} </div>
{#if runner.phone} </div>
<div class="text-sm text-gray-500">{runner.phone}</div> </div>
{/if} </td>
{#if runner.address.address1 !== null} <td class="px-6 py-4 whitespace-nowrap">
{runner.address.address1}<br /> {#if runner.email}
{runner.address.address2 || ''}<br /> <div class="text-sm text-gray-500">{runner.email}</div>
{runner.address.postalcode} {/if}
{runner.address.city} {#if runner.phone}
{runner.address.country} <div class="text-sm text-gray-500">{runner.phone}</div>
{/if} {/if}
</td> {#if runner.address.address1 !== null}
<td class="px-6 py-4 whitespace-nowrap"> {runner.address.address1}<br />
{#if runner.group.responseType === 'RUNNERTEAM'} {runner.address.address2 || ''}<br />
<a {runner.address.postalcode}
href="../teams/{runner.group.id}" {runner.address.city}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> {runner.address.country}
{/if} {/if}
{#if runner.group.responseType === 'RUNNERORGANIZATION'} </td>
<a <td class="px-6 py-4 whitespace-nowrap">
href="../orgs/{runner.group.id}" {#if runner.group.responseType === 'RUNNERTEAM'}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a> <a
{/if} href="../teams/{runner.group.id}"
</td> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
<td class="px-6 py-4 whitespace-nowrap"> {/if}
{runner.distance} {#if runner.group.responseType === 'RUNNERORGANIZATION'}
</td> <a
{#if active_deletes[runner.id] === true} href="../orgs/{runner.group.id}"
<td class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> {/if}
<button </td>
on:click={() => { <td class="px-6 py-4 whitespace-nowrap">
active_deletes[runner.id] = false; {runner.distance}
}} </td>
tabindex="0" {#if active_deletes[runner.id] === true}
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> <td
<button class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
on:click={() => { <button
RunnerService.runnerControllerRemove(runner.id, true) on:click={() => {
.then((resp) => { active_deletes[runner.id] = false;
current_runners = current_runners.filter((obj) => obj.id !== runner.id); }}
}) tabindex="0"
.catch((err) => {}); class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
}} <button
tabindex="0" on:click={() => {
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> RunnerService.runnerControllerRemove(runner.id, true)
</td> .then((resp) => {
{:else} current_runners = current_runners.filter((obj) => obj.id !== runner.id);
<td })
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> .catch((err) => {});
<a }}
href="./{runner.id}" tabindex="0"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} </td>
<button {:else}
on:click={() => { <td
active_deletes[runner.id] = true; class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
}} <a
tabindex="0" href="./{runner.id}"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{/if} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
</td> <button
{/if} on:click={() => {
</tr> active_deletes[runner.id] = true;
{/if} }}
{/if} tabindex="0"
{/each} class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
</tbody> {/if}
</table> </td>
</div> {/if}
{/if} </tr>
{:catch error} {/if}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> {/if}
<span class="inline-block align-middle mr-8"> {/each}
<b class="capitalize">{$_('general_promise_error')}</b> </tbody>
{error} </table>
</span> </div>
</div> {/if}
{/await} {:catch error}
{/if} <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

@@ -4,6 +4,9 @@
import { MeService } from "@odit/lfk-client-js"; import { MeService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import ConfirmProfileDeletion from "./ConfirmProfileDeletion.svelte"; import ConfirmProfileDeletion from "./ConfirmProfileDeletion.svelte";
import PasswordStrength, {
password_strong_enough_and_equal,
} from "../auth/PasswordStrength.svelte";
$: data_loaded = false; $: data_loaded = false;
$: delete_triggered = false; $: delete_triggered = false;
$: original_data = {}; $: original_data = {};
@@ -15,8 +18,10 @@
JSON.stringify(editable) === JSON.stringify(original_data) JSON.stringify(editable) === JSON.stringify(original_data)
); );
$: save_enabled = changes_performed && isEmail(editable.email); $: save_enabled = changes_performed && isEmail(editable.email);
$: update_password_enabled = $: update_password_enabled = password_strong_enough_and_equal(
password_change.length > 0 && password_change === password_confirm; password_change,
password_confirm
);
const user_promise = MeService.meControllerGet().then((data) => { const user_promise = MeService.meControllerGet().then((data) => {
data_loaded = true; data_loaded = true;
data.groups = data.groups.map((g) => g.id); data.groups = data.groups.map((g) => g.id);
@@ -45,7 +50,7 @@
function changePassword() { function changePassword() {
if (data_loaded === true && update_password_enabled) { if (data_loaded === true && update_password_enabled) {
Toastify({ Toastify({
text: $_('changing-your-password'), text: $_("changing-your-password"),
duration: 2500, duration: 2500,
}).showToast(); }).showToast();
let postdata = Object.assign({}, original_data); let postdata = Object.assign({}, original_data);
@@ -56,7 +61,7 @@
password_change = ""; password_change = "";
postdata = {}; postdata = {};
Toastify({ Toastify({
text: $_('password-changed'), text: $_("password-changed"),
duration: 2500, duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
@@ -242,10 +247,7 @@
class="border-gray-300 placeholder-gray-500 appearance-none rounded-md relative block w-full px-3 py-2 border focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" class="border-gray-300 placeholder-gray-500 appearance-none rounded-md relative block w-full px-3 py-2 border focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder={$_('password')} /> placeholder={$_('password')} />
</div> </div>
{#if password_change != password_confirm && password_change.length > 0} <PasswordStrength bind:password_change bind:password_confirm />
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">{$_('passwords-dont-match')}</span>
{/if}
</div> </div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6"> <div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button <button
@@ -257,9 +259,9 @@
{$_('update-password')} {$_('update-password')}
</button> </button>
{#if update_password_enabled} {#if update_password_enabled}
<p> <p>
{$_('after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that')} {$_('after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that')}
</p> </p>
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -1,292 +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";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
let [teamdata, original, delete_team, orgs, contacts, modal_open] = [ import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
{}, let [teamdata, original, delete_team, orgs, contacts, modal_open] = [
{}, {},
{}, {},
[], {},
[], [],
false, [],
]; false,
export let params; ];
export let import_modal_open = false; export let params;
$: delete_triggered = false; export let import_modal_open = false;
$: save_enabled = !data_changed && teamdata.parentGroup != null; $: delete_triggered = false;
$: data_loaded = false; $: save_enabled = !data_changed && teamdata.parentGroup != null;
$: data_changed = JSON.stringify(teamdata) === JSON.stringify(original); $: data_loaded = false;
$: sponsoring_contracts_show = true; $: data_changed = JSON.stringify(teamdata) === JSON.stringify(original);
$: generate_teams = [original]; $: sponsoring_contracts_show = true;
$: group = {}; $: cards_show = true;
$: contact = {}; $: generate_teams = [original];
// $: group = {};
const getContactLabel = (option) => $: contact = {};
option.firstname + " " + (option.middlename || "") + " " + option.lastname; //
const promise = RunnerTeamService.runnerTeamControllerGetOne( const getContactLabel = (option) =>
params.teamid option.firstname + " " + (option.middlename || "") + " " + option.lastname;
).then((value) => { const promise = RunnerTeamService.runnerTeamControllerGetOne(
data_loaded = true; params.teamid
teamdata = Object.assign(teamdata, value); ).then((value) => {
original = Object.assign(original, value); data_loaded = true;
RunnerOrganizationService.runnerOrganizationControllerGetAll().then( teamdata = Object.assign(teamdata, value);
(val) => { original = Object.assign(original, value);
orgs = val.map((r) => { RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
return { label: r.name, value: r }; (val) => {
}); orgs = val.map((r) => {
group = orgs.find((g) => g.value.id == teamdata.parentGroup.id); return { label: r.name, value: r };
} });
); group = orgs.find((g) => g.value.id == teamdata.parentGroup.id);
GroupContactService.groupContactControllerGetAll().then((val) => { }
contacts = val.map((r) => { );
return { label: getContactLabel(r), value: r }; GroupContactService.groupContactControllerGetAll().then((val) => {
}); contacts = val.map((r) => {
if (teamdata.contact) { return { label: getContactLabel(r), value: r };
contact = contacts.find((g) => g.value.id == teamdata.contact.id); });
} else { if (teamdata.contact) {
contact = null; contact = contacts.find((g) => g.value.id == teamdata.contact.id);
} } else {
}); contact = null;
}); }
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 </script>
current_runners={[]}
on:cancelDelete={(event) => { <ImportRunnerModal
import_modal_open = false; current_runners={[]}
}} on:cancelDelete={(event) => {
passed_team={teamdata} import_modal_open = false;
passed_orgs={[]} }}
passed_org={{}} passed_team={teamdata}
opened_from="TeamDetail" passed_orgs={[]}
bind:import_modal_open /> passed_org={{}}
<ConfirmTeamDeletion bind:modal_open bind:delete_team /> opened_from="TeamDetail"
{#if data_loaded} bind:import_modal_open />
<section class="container p-5"> <ConfirmTeamDeletion bind:modal_open bind:delete_team />
<div class="mb-8 text-3xl font-extrabold leading-tight"> {#if data_loaded}
{original.name} <section class="container p-5">
<span data-id="org_actions_${teamdata.id}"> <div class="mb-8 text-3xl font-extrabold leading-tight">
<GenerateSponsoringContracts {original.name}
bind:sponsoring_contracts_show <span data-id="org_actions_${teamdata.id}">
bind:generate_teams /> <GenerateSponsoringContracts
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} bind:sponsoring_contracts_show
<button bind:generate_teams />
on:click={() => { <GenerateRunnerCards
import_modal_open = true; bind:cards_show
}} bind:generate_teams />
type="button" {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
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"> <button
{$_('import-runners')} on:click={() => {
</button> import_modal_open = true;
{/if} }}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} type="button"
{#if delete_triggered} 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">
<button {$_('import-runners')}
on:click={deleteTeam} </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">{$_('confirm-delete')}</button> {/if}
<button {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
on:click={() => { {#if delete_triggered}
delete_triggered = !delete_triggered; <button
}} on:click={deleteTeam}
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> 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>
{/if} <button
{#if !delete_triggered} on:click={() => {
<button delete_triggered = !delete_triggered;
on:click={() => { }}
delete_triggered = true; 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>
}} {/if}
type="button" {#if !delete_triggered}
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> <button
{/if} on:click={() => {
{/if} delete_triggered = true;
{#if !delete_triggered} }}
<button type="button"
on:click={submit} 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>
disabled={!save_enabled} {/if}
class:opacity-50={!save_enabled} {/if}
type="button" {#if !delete_triggered}
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> <button
{/if} on:click={submit}
</span> disabled={!save_enabled}
</div> class:opacity-50={!save_enabled}
<div class="flex flex-row mb-4"> type="button"
<div class="w-full"> 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>
<nav class="w-full flex"> {/if}
<ol class="list-none flex flex-row items-center justify-start"> </span>
<li class="mr-2 flex items-center"> </div>
<svg <div class="flex flex-row mb-4">
stroke="currentColor" <div class="w-full">
fill="none" <nav class="w-full flex">
stroke-width="2" <ol class="list-none flex flex-row items-center justify-start">
viewBox="0 0 24 24" <li class="mr-2 flex items-center">
stroke-linecap="round" <svg
stroke-linejoin="round" stroke="currentColor"
class="h-3 w-3 stroke-current" fill="none"
height="1em" stroke-width="2"
width="1em" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round"
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> stroke-linejoin="round"
<polyline points="9 22 9 12 15 12 15 22" /></svg> class="h-3 w-3 stroke-current"
</li> height="1em"
<li class="flex items-center"> width="1em"
<a class="mr-2" href="/">Home</a><svg xmlns="http://www.w3.org/2000/svg"><path
stroke="currentColor" d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
fill="none" <polyline points="9 22 9 12 15 12 15 22" /></svg>
stroke-width="2" </li>
viewBox="0 0 24 24" <li class="flex items-center">
stroke-linecap="round" <a class="mr-2" href="/">Home</a><svg
stroke-linejoin="round" stroke="currentColor"
class="h-3 w-3 mr-2 stroke-current" fill="none"
height="1em" stroke-width="2"
width="1em" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><line stroke-linecap="round"
x1="5" stroke-linejoin="round"
y1="12" class="h-3 w-3 mr-2 stroke-current"
x2="19" height="1em"
y2="12" /> width="1em"
<polyline points="12 5 19 12 12 19" /></svg> xmlns="http://www.w3.org/2000/svg"><line
</li> x1="5"
<li class="mr-2 flex items-center"> y1="12"
<svg x2="19"
class="flex-shrink-0 w-5 h-5 mr-2" y2="12" />
fill="currentColor" <polyline points="12 5 19 12 12 19" /></svg>
width="24" </li>
height="24" <li class="mr-2 flex items-center">
xmlns="http://www.w3.org/2000/svg" <svg
viewBox="0 0 640 512"><path class="flex-shrink-0 w-5 h-5 mr-2"
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> width="24"
</li> height="24"
<li class="flex items-center"> xmlns="http://www.w3.org/2000/svg"
<a class="mr-2" href="./">Teams</a><svg viewBox="0 0 640 512"><path
stroke="currentColor" fill="currentColor"
fill="none" 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>
stroke-width="2" </li>
viewBox="0 0 24 24" <li class="flex items-center">
stroke-linecap="round" <a class="mr-2" href="./">Teams</a><svg
stroke-linejoin="round" stroke="currentColor"
class="h-3 w-3 mr-2 stroke-current" fill="none"
height="1em" stroke-width="2"
width="1em" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><line stroke-linecap="round"
x1="5" stroke-linejoin="round"
y1="12" class="h-3 w-3 mr-2 stroke-current"
x2="19" height="1em"
y2="12" /> width="1em"
<polyline points="12 5 19 12 12 19" /></svg> xmlns="http://www.w3.org/2000/svg"><line
</li> x1="5"
<li class="flex items-center"> y1="12"
<span class="mr-2">Team-Details #{params.teamid}</span> x2="19"
</li> y2="12" />
</ol> <polyline points="12 5 19 12 12 19" /></svg>
</nav> </li>
</div> <li class="flex items-center">
</div> <span class="mr-2">Team-Details #{params.teamid}</span>
<div class="text-sm w-full"> </li>
<label for="name" class="font-medium text-gray-700">Name</label> </ol>
<input </nav>
autocomplete="off" </div>
placeholder="Name" </div>
type="text" <div class="text-sm w-full">
bind:value={teamdata.name} <label for="name" class="font-medium text-gray-700">Name</label>
name="name" <input
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" /> autocomplete="off"
</div> placeholder="Name"
<div class="text-sm w-full"> type="text"
<label bind:value={teamdata.name}
for="contact" name="name"
class="font-medium text-gray-700">{$_('contact')}</label> 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" />
<Select </div>
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" <div class="text-sm w-full">
itemFilter={(label, filterText, option) => label <label
.toLowerCase() for="contact"
.includes( class="font-medium text-gray-700">{$_('contact')}</label>
filterText.toLowerCase() <Select
) || option.value.id 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"
.toString() itemFilter={(label, filterText, option) => label
.startsWith(filterText.toLowerCase())} .toLowerCase()
items={contacts} .includes(
showChevron={true} filterText.toLowerCase()
placeholder={$_('no-contact-selected')} ) || option.value.id
noOptionsMessage={$_('no-contact-found')} .toString()
bind:selectedValue={contact} .startsWith(filterText.toLowerCase())}
on:select={(selectedValue) => (teamdata.contact = selectedValue.detail.value)} items={contacts}
on:clear={() => (teamdata.contact = null)} /> showChevron={true}
</div> placeholder={$_('no-contact-selected')}
<div class="text-sm w-full"> noOptionsMessage={$_('no-contact-found')}
<label bind:selectedValue={contact}
for="org" on:select={(selectedValue) => (teamdata.contact = selectedValue.detail.value)}
class="font-medium text-gray-700">{$_('organization')}</label> on:clear={() => (teamdata.contact = null)} />
<Select </div>
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" <div class="text-sm w-full">
itemFilter={(label, filterText, option) => label <label
.toLowerCase() for="org"
.includes( class="font-medium text-gray-700">{$_('organization')}</label>
filterText.toLowerCase() <Select
) || option.id.value 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"
.toString() itemFilter={(label, filterText, option) => label
.startsWith(filterText.toLowerCase())} .toLowerCase()
items={orgs} .includes(
showChevron={true} filterText.toLowerCase()
placeholder={$_('search-for-an-organization-by-name-or-id')} ) || option.id.value
noOptionsMessage={$_('no-organizations-found')} .toString()
bind:selectedValue={group} .startsWith(filterText.toLowerCase())}
on:select={(selectedValue) => (teamdata.parentGroup = selectedValue.detail.value)} items={orgs}
on:clear={() => (teamdata.parentGroup = null)} /> showChevron={true}
</div> placeholder={$_('search-for-an-organization-by-name-or-id')}
</section> noOptionsMessage={$_('no-organizations-found')}
{:else} bind:selectedValue={group}
{#await promise} on:select={(selectedValue) => (teamdata.parentGroup = selectedValue.detail.value)}
{$_('team-detail-is-being-loaded')} on:clear={() => (teamdata.parentGroup = null)} />
{:catch error} </div>
<PromiseError /> </section>
{/await} {:else}
{/if} {#await promise}
{$_('team-detail-is-being-loaded')}
{:catch error}
<PromiseError />
{/await}
{/if}

View File

@@ -1,210 +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";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
$: searchvalue = ""; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: active_deletes = []; $: searchvalue = "";
$: sponsoring_contracts_show = current_teams.some( $: active_deletes = [];
(r) => r.is_selected === true $: sponsoring_contracts_show = current_teams.some(
); (r) => r.is_selected === true
$: generate_teams = current_teams.filter((r) => r.is_selected === true); );
export let current_teams = []; $: cards_show = current_teams.some(
let modal_open = false; (r) => r.is_selected === true
let delete_team = {}; );
usersstore.subscribe((val) => { $: generate_teams = current_teams.filter((r) => r.is_selected === true);
current_teams = val; export let current_teams = [];
}); let modal_open = false;
teams_promise.then((data) => { let delete_team = {};
usersstore.set(data); usersstore.subscribe((val) => {
}); current_teams = val;
</script> });
teams_promise.then((data) => {
<ConfirmTeamDeletion usersstore.set(data);
on:cancelDelete={(event) => { });
modal_open = false; </script>
active_deletes[event.detail.id] = false;
}} <ConfirmTeamDeletion
bind:modal_open on:cancelDelete={(event) => {
bind:delete_team /> modal_open = false;
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} active_deletes[event.detail.id] = false;
{#await teams_promise} }}
<div bind:modal_open
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" bind:delete_team />
role="alert"> {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
<p class="font-bold">{$_('teams-are-being-loaded')}</p> {#await teams_promise}
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <div
</div> class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
{:then} role="alert">
{#if current_teams.length === 0} <p class="font-bold">{$_('teams-are-being-loaded')}</p>
<TeamsEmptyState /> <p class="text-sm">{$_('this-might-take-a-moment')}</p>
{:else} </div>
<input {:then}
type="search" {#if current_teams.length === 0}
bind:value={searchvalue} <TeamsEmptyState />
placeholder={$_('datatable.search')} {:else}
aria-label={$_('datatable.search')} <input
class="gridjs-input gridjs-search-input mb-4" /> type="search"
<div class="h-12"> bind:value={searchvalue}
<GenerateSponsoringContracts placeholder={$_('datatable.search')}
bind:sponsoring_contracts_show aria-label={$_('datatable.search')}
bind:generate_teams /> class="gridjs-input gridjs-search-input mb-4" />
</div> <div class="h-12">
<div <GenerateSponsoringContracts
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> bind:sponsoring_contracts_show
<table class="divide-y divide-gray-200 w-full"> bind:generate_teams />
<thead class="bg-gray-50"> <GenerateRunnerCards
<tr> bind:cards_show
<th bind:generate_teams />
scope="col" </div>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <div
<span class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
on:click={() => { <table class="divide-y divide-gray-200 w-full">
const newstate = !current_teams.some((r) => r.is_selected === true); <thead class="bg-gray-50">
current_teams = current_teams.map((r) => { <tr>
r.is_selected = newstate; <th
return r; scope="col"
}); class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
}} <span
class="underline cursor-pointer select-none">{#if current_teams.some((r) => r.is_selected === true)} on:click={() => {
{$_('deselect-all')} const newstate = !current_teams.some((r) => r.is_selected === true);
{:else}{$_('select-all')}{/if} current_teams = current_teams.map((r) => {
</span> r.is_selected = newstate;
</th> return r;
<th });
scope="col" }}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="underline cursor-pointer select-none">{#if current_teams.some((r) => r.is_selected === true)}
{$_('name')} {$_('deselect-all')}
</th> {:else}{$_('select-all')}{/if}
<th </span>
scope="col" </th>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th
{$_('organization')} scope="col"
</th> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th {$_('name')}
scope="col" </th>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th
{$_('contact')} scope="col"
</th> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th scope="col" class="relative px-6 py-3"> {$_('organization')}
<span class="sr-only">{$_('action')}</span> </th>
</th> <th
</tr> scope="col"
</thead> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<tbody class="divide-y divide-gray-200"> {$_('contact')}
{#each current_teams as t} </th>
{#if Object.values(t) <th scope="col" class="relative px-6 py-3">
.toString() <span class="sr-only">{$_('action')}</span>
.toLowerCase() </th>
.includes(searchvalue)} </tr>
<tr data-rowid="team_{t.id}"> </thead>
<td class="px-6 py-4 whitespace-nowrap"> <tbody class="divide-y divide-gray-200">
<input {#each current_teams as t}
bind:checked={t.is_selected} {#if Object.values(t)
type="checkbox" .toString()
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> .toLowerCase()
</td> .includes(searchvalue)}
<td class="px-6 py-4 whitespace-nowrap"> <tr data-rowid="team_{t.id}">
<div class="flex items-center"> <td class="px-6 py-4 whitespace-nowrap">
<div class="ml-4"> <input
<div class="text-sm font-medium text-gray-900"> bind:checked={t.is_selected}
{t.name} type="checkbox"
</div> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div> </td>
</div> <td class="px-6 py-4 whitespace-nowrap">
</td> <div class="flex items-center">
<td class="px-6 py-4 whitespace-nowrap"> <div class="ml-4">
<div class="flex items-center"> <div class="text-sm font-medium text-gray-900">
<div class="ml-4"> {t.name}
<div class="text-sm font-medium text-gray-900"> </div>
{#if t.parentGroup} </div>
<a </div>
href="../orgs/{t.parentGroup.id}" </td>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a> <td class="px-6 py-4 whitespace-nowrap">
{:else}{$_('no-organization-specified')}{/if} <div class="flex items-center">
</div> <div class="ml-4">
</div> <div class="text-sm font-medium text-gray-900">
</div> {#if t.parentGroup}
</td> <a
<td class="px-6 py-4 whitespace-nowrap"> href="../orgs/{t.parentGroup.id}"
<div class="flex items-center"> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a>
<div class="ml-4"> {:else}{$_('no-organization-specified')}{/if}
<div class="text-sm font-medium text-gray-900"> </div>
{#if t.contact} </div>
<a </div>
href="../contacts/{t.contact.id}" </td>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname} <td class="px-6 py-4 whitespace-nowrap">
{t.contact.middlename || ''} <div class="flex items-center">
{t.contact.lastname}</a> <div class="ml-4">
{:else}{$_('no-contact-specified')}{/if} <div class="text-sm font-medium text-gray-900">
</div> {#if t.contact}
</div> <a
</div> href="../contacts/{t.contact.id}"
</td> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname}
{#if active_deletes[t.id] === true} {t.contact.middlename || ''}
<td {t.contact.lastname}</a>
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> {:else}{$_('no-contact-specified')}{/if}
<button </div>
on:click={() => { </div>
active_deletes[t.id] = false; </div>
}} </td>
tabindex="0" {#if active_deletes[t.id] === true}
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel <td
Delete</button> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button <button
on:click={() => { on:click={() => {
RunnerTeamService.runnerTeamControllerRemove(t.id, false) active_deletes[t.id] = false;
.then((resp) => { }}
current_teams = current_teams.filter((obj) => obj.id !== t.id); tabindex="0"
Toastify({ class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel
text: $_('organization-deleted'), Delete</button>
duration: 500, <button
backgroundColor: on:click={() => {
'linear-gradient(to right, #00b09b, #96c93d)', RunnerTeamService.runnerTeamControllerRemove(t.id, false)
}).showToast(); .then((resp) => {
}) current_teams = current_teams.filter((obj) => obj.id !== t.id);
.catch((err) => { Toastify({
modal_open = true; text: $_('organization-deleted'),
delete_team = t; duration: 500,
}); backgroundColor:
}} 'linear-gradient(to right, #00b09b, #96c93d)',
tabindex="0" }).showToast();
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> })
</td> .catch((err) => {
{:else} modal_open = true;
<td delete_team = t;
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> });
<a }}
href="./{t.id}" tabindex="0"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')} </td>
<button {:else}
on:click={() => { <td
active_deletes[t.id] = true; class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
}} <a
tabindex="0" href="./{t.id}"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{/if} {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
</td> <button
{/if} on:click={() => {
</tr> active_deletes[t.id] = true;
{/if} }}
{/each} tabindex="0"
</tbody> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
</table> {/if}
</div> </td>
{/if} {/if}
{:catch error} </tr>
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> {/if}
<span class="inline-block align-middle mr-8"> {/each}
<b class="capitalize">{$_('general_promise_error')}</b> </tbody>
{error} </table>
</span> </div>
</div> {/if}
{/await} {:catch error}
{/if} <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

@@ -5,6 +5,9 @@
import { UserService } from "@odit/lfk-client-js"; import { UserService } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import PasswordStrength, {
password_strong_enough,
} from "../auth/PasswordStrength.svelte";
export let modal_open; export let modal_open;
export let current_users; export let current_users;
let firstname_input; let firstname_input;
@@ -28,7 +31,10 @@
$: isLastnameValid = lastname_input_value.trim().length !== 0; $: isLastnameValid = lastname_input_value.trim().length !== 0;
$: isFirstnameValid = firstname_input_value.trim().length !== 0; $: isFirstnameValid = firstname_input_value.trim().length !== 0;
$: createbtnenabled = $: createbtnenabled =
isFirstnameValid && isLastnameValid && isPasswordValid && isEmailValid; isFirstnameValid &&
isLastnameValid &&
password_strong_enough(password_input_value) &&
isEmailValid;
(function () { (function () {
document.onkeydown = function (e) { document.onkeydown = function (e) {
e = e || window.event; e = e || window.event;
@@ -203,12 +209,8 @@
type="password" type="password"
name="password" name="password"
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="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 !isPasswordValid} <PasswordStrength
<span bind:password_change={password_input_value} />
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('password-is-required')}
</span>
{/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label

View File

@@ -53,6 +53,7 @@
"change-your-password-here": "Hier kannst du dein Passwort ändern", "change-your-password-here": "Hier kannst du dein Passwort ändern",
"changing-your-password": "Passwort wird geändert", "changing-your-password": "Passwort wird geändert",
"city": "Stadt", "city": "Stadt",
"click-to-copy-the-link-into-your-clipboard": "Klicke auf den Link, um ihn in deine Zwischenablage zu kopieren",
"click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren", "click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren",
"close": "Schließen", "close": "Schließen",
"code": "Code", "code": "Code",
@@ -72,6 +73,7 @@
"contact-is-not-a-member-in-any-group": "Kontakt gehört zu keiner Gruppe", "contact-is-not-a-member-in-any-group": "Kontakt gehört zu keiner Gruppe",
"contacts": "Kontakte", "contacts": "Kontakte",
"contacts-are-being-loaded": "Kontakte werden geladen ...", "contacts-are-being-loaded": "Kontakte werden geladen ...",
"copied-link-to-clipboard": "Link wurde in die Zwischenablage kopiert",
"copied-token-to-clipboard": "Token wurde in die Zwischenablage kopiert", "copied-token-to-clipboard": "Token wurde in die Zwischenablage kopiert",
"count_organizations": "Organisationen (Anzahl)", "count_organizations": "Organisationen (Anzahl)",
"count_teams": "Teams (Anzahl)", "count_teams": "Teams (Anzahl)",
@@ -187,6 +189,7 @@
"geerbte": "geerbte", "geerbte": "geerbte",
"general-stats": "Allgemeine Statistiken", "general-stats": "Allgemeine Statistiken",
"general_promise_error": "😢 Ein unbekannter Fehler ist aufgetreten", "general_promise_error": "😢 Ein unbekannter Fehler ist aufgetreten",
"generate-runnercards": "Läuferkarten generieren",
"generate-sponsoring-contract": "Sponsoringvertrag generieren", "generate-sponsoring-contract": "Sponsoringvertrag generieren",
"generate-sponsoring-contracts": "Sponsoringverträge generieren", "generate-sponsoring-contracts": "Sponsoringverträge generieren",
"generating-pdf": "Pdf wird generiert...", "generating-pdf": "Pdf wird generiert...",
@@ -216,7 +219,7 @@
"internal-error": "Interner Fehler", "internal-error": "Interner Fehler",
"invalid": "Ungültig", "invalid": "Ungültig",
"invalid-mail-reset": "Das ist keine gültige E-Mail", "invalid-mail-reset": "Das ist keine gültige E-Mail",
"just-enter-how-many-you-want-and-the-system-will-create-them": "Geb einfach ein, wie viele Blankokarten das System erstellen soll.", "just-enter-how-many-you-want-and-the-system-will-create-them": "Gebe einfach ein, wie viele Blankokarten das System erstellen soll.",
"laeufer-hinzufuegen": "Läufer:in hinzufügen", "laeufer-hinzufuegen": "Läufer:in hinzufügen",
"laeufer-importieren": "Läufer:innen importieren", "laeufer-importieren": "Läufer:innen importieren",
"laptime": "Rundenzeit", "laptime": "Rundenzeit",
@@ -242,6 +245,10 @@
"middle-name": "Mittelname", "middle-name": "Mittelname",
"minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)", "minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)",
"minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein", "minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein",
"must-be-at-least-10-characters-long": "Passwort muss mindestens 10 Zeichen lang sein!",
"must-contain-a-lowercase-letter": "Passwort muss einen Großbuchstaben enthalten!",
"must-contain-a-number": "Passwort muss eine Zahl enthalten!",
"must-contain-a-uppercase-letter": "Passwort muss einen Kleinbuchstaben enthalten!",
"name": "Name", "name": "Name",
"name-is-required": "Der Gruppenname muss angegeben werden", "name-is-required": "Der Gruppenname muss angegeben werden",
"new-password": "Neues Passwort", "new-password": "Neues Passwort",
@@ -273,7 +280,7 @@
"password-reset-in-progress": "Passwort wird zurückgesetzt...", "password-reset-in-progress": "Passwort wird zurückgesetzt...",
"password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.", "password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.",
"password-reset-successful": "Passwort erfolgreich zurückgesetzt!", "password-reset-successful": "Passwort erfolgreich zurückgesetzt!",
"passwords-dont-match": "Die Passwörter stimmen nicht überein.", "passwords-dont-match": "Die Passwörter stimmen nicht überein!",
"pdf-generation-failed": "PDF Generierung fehlgeschlagen!", "pdf-generation-failed": "PDF Generierung fehlgeschlagen!",
"pdf-successfully-generated": "PDF wurde erfolgreich generiert!", "pdf-successfully-generated": "PDF wurde erfolgreich generiert!",
"pdfs-successfully-generated": "Alle PDFs wurden generiert!", "pdfs-successfully-generated": "Alle PDFs wurden generiert!",
@@ -335,6 +342,7 @@
"search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)", "search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)",
"select-all": "Alle auswählen", "select-all": "Alle auswählen",
"select-language": "Sprache auswählen", "select-language": "Sprache auswählen",
"selfservice-registration": "Selfservice Registrierung",
"send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services", "send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services",
"set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen", "set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen",
"settings": "Einstellungen", "settings": "Einstellungen",
@@ -411,6 +419,7 @@
"you-can-provide-a-runner-but-you-dont-have-to": "Du kannst eine Läufer:in angeben, musst aber nicht.", "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-dont-have-any-scanstations-yet": "Es gibt noch keine Scannerstationen",
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben", "you-have-to-provide-an-organization": "Du musst eine Organisation angeben",
"you-have-to-save-your-changes-to-generate-a-link": "Du musst deine Änderungen speichern, um einen Link zu generieren.",
"you-must-create-at-least-one-card-or-cancel": "Du musst mindestens eine Blankokarte erstellen (oder abbrechen).", "you-must-create-at-least-one-card-or-cancel": "Du musst mindestens eine Blankokarte erstellen (oder abbrechen).",
"zip-postal-code": "Postleitzahl" "zip-postal-code": "Postleitzahl"
} }

View File

@@ -53,6 +53,7 @@
"change-your-password-here": "Change your password here", "change-your-password-here": "Change your password here",
"changing-your-password": "Changing your password", "changing-your-password": "Changing your password",
"city": "City", "city": "City",
"click-to-copy-the-link-into-your-clipboard": "Click to copy the link into your clipboard",
"click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard", "click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard",
"close": "Close", "close": "Close",
"code": "Code", "code": "Code",
@@ -72,6 +73,7 @@
"contact-is-not-a-member-in-any-group": "Contact is not a member in any group", "contact-is-not-a-member-in-any-group": "Contact is not a member in any group",
"contacts": "Contacts", "contacts": "Contacts",
"contacts-are-being-loaded": "contacts are being loaded...", "contacts-are-being-loaded": "contacts are being loaded...",
"copied-link-to-clipboard": "Copied link to clipboard",
"copied-token-to-clipboard": "Copied token to clipboard", "copied-token-to-clipboard": "Copied token to clipboard",
"count_organizations": "# Organizations", "count_organizations": "# Organizations",
"count_teams": "# Teams", "count_teams": "# Teams",
@@ -187,6 +189,7 @@
"geerbte": "inherited", "geerbte": "inherited",
"general-stats": "General Stats", "general-stats": "General Stats",
"general_promise_error": "😢 Error", "general_promise_error": "😢 Error",
"generate-runnercards": "Generate Runnercards",
"generate-sponsoring-contract": "generate sponsoring contract", "generate-sponsoring-contract": "generate sponsoring contract",
"generate-sponsoring-contracts": "generate sponsoring contracts", "generate-sponsoring-contracts": "generate sponsoring contracts",
"generating-pdf": "generating PDF...", "generating-pdf": "generating PDF...",
@@ -242,6 +245,10 @@
"middle-name": "Middle name", "middle-name": "Middle name",
"minimum-lap-time-in-s": "minimum lap time in s", "minimum-lap-time-in-s": "minimum lap time in s",
"minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0", "minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0",
"must-be-at-least-10-characters-long": "Must be at least 10 characters long!",
"must-contain-a-lowercase-letter": "Must contain a lowercase letter!",
"must-contain-a-number": "Must contain a number!",
"must-contain-a-uppercase-letter": "Must contain a uppercase letter!",
"name": "Name", "name": "Name",
"name-is-required": "Name is required", "name-is-required": "Name is required",
"new-password": "New password", "new-password": "New password",
@@ -273,7 +280,7 @@
"password-reset-in-progress": "Password Reset in Progress...", "password-reset-in-progress": "Password Reset in Progress...",
"password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".", "password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".",
"password-reset-successful": "Password Reset successful!", "password-reset-successful": "Password Reset successful!",
"passwords-dont-match": "Passwords don't match", "passwords-dont-match": "Passwords don't match!",
"pdf-generation-failed": "PDF generation failed!", "pdf-generation-failed": "PDF generation failed!",
"pdf-successfully-generated": "PDF successfully generated!", "pdf-successfully-generated": "PDF successfully generated!",
"pdfs-successfully-generated": "PDFs successfully generated!", "pdfs-successfully-generated": "PDFs successfully generated!",
@@ -335,6 +342,7 @@
"search-for-runner-by-name-or-id": "Search for runner (by name or id)", "search-for-runner-by-name-or-id": "Search for runner (by name or id)",
"select-all": "select all", "select-all": "select all",
"select-language": "Select language", "select-language": "Select language",
"selfservice-registration": "Selfservice registration",
"send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services", "send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services",
"set-the-user-active-inactive": "set the user active/ inactive", "set-the-user-active-inactive": "set the user active/ inactive",
"settings": "Settings", "settings": "Settings",
@@ -412,6 +420,7 @@
"you-can-provide-a-runner-but-you-dont-have-to": "You can provide a runner, but you don't have to.", "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-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-have-to-provide-an-organization": "You have to provide an organization",
"you-have-to-save-your-changes-to-generate-a-link": "You have to save your changes to generate a link.",
"you-must-create-at-least-one-card-or-cancel": "You must create at least one card (or cancel).", "you-must-create-at-least-one-card-or-cancel": "You must create at least one card (or cancel).",
"zip-postal-code": "ZIP/ postal code" "zip-postal-code": "ZIP/ postal code"
} }