Compare commits

...

130 Commits

Author SHA1 Message Date
fa55fce76e Merge branch 'feature/119-Certificate_generation' of git.odit.services:lfk/frontend into feature/119-Certificate_generation 2021-04-03 19:59:20 +02:00
f47d5e347d Copy-paste fix
ref #119
2021-04-03 19:59:18 +02:00
7488a8b597 Copy-paste fix
ref #119
2021-04-03 19:58:24 +02:00
2e3ac154be Implemented generation for orgs
ref #119
2021-04-03 19:52:41 +02:00
2472640755 Implemented generation for teams
ref #119
2021-04-03 19:51:01 +02:00
7b685d6cad Added certificate generation from runner overview and detail
ref #119
2021-04-03 19:48:31 +02:00
17f6f4e616 Added i18n
ref #119
2021-04-03 19:46:17 +02:00
48cfc15cfb Removed useless console log
ref #119
2021-04-03 19:44:57 +02:00
bb9b779cee You can now generate certificates from the runner overview
ref #119
2021-04-03 19:44:26 +02:00
af63ce67ae Added basic certificate generation component
ref #119
2021-04-03 19:38:54 +02:00
5cc4871ec4 new license file version [CI SKIP] 2021-04-03 17:18:28 +00:00
c8cfe669b8 Merge pull request 'feature/108_vite_migration' (#118) from feature/108_vite_migration into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #118
close #108
2021-04-03 17:17:23 +00:00
8b74d6d759 bump @odit/lfk-client-js@0.10.1
ref #108
2021-04-03 19:16:53 +02:00
a9227768de 🐞 fix await for esNext
ref #108
2021-04-03 19:13:05 +02:00
d966e1d4de Merge branch 'dev' into feature/108_vite_migration
# Conflicts:
#	index.html
#	package.json
#	public/licenses.json
#	src/App.svelte
#	src/components/orgs/OrgOverview.svelte
#	src/components/teams/TeamsOverview.svelte
2021-04-03 19:10:10 +02:00
ceb2146c1b 🔨 dev container open
ref #108
2021-04-03 18:31:03 +02:00
8d006d8c74 version bump: vite-plugin-windicss@0.12.2
ref #108
2021-04-03 18:22:00 +02:00
777304f259 🔨🔥 alpine based devcontainer with working yarn PnP
ref #108
2021-04-02 21:57:56 +02:00
12433f7c23 🧹 reorder + fix package
ref #108
2021-04-02 21:56:57 +02:00
44b53da345 🚚 move @svitejs/vite-plugin-svelte to @sveltejs/vite-plugin-svelte
ref #108
2021-04-02 21:47:43 +02:00
ab45fc144e upgrade vite-plugin-windicss@0.12.1
ref #108
2021-04-02 21:20:48 +02:00
e99e9e0708 update licenses.json
ref #108
2021-04-02 21:20:05 +02:00
467404bfc8 🐞 fix main.js linking
ref #108
2021-04-02 21:19:49 +02:00
ce50fa2a62 🧹 drop unused dependencies
ref #108
2021-04-02 21:19:29 +02:00
10a011d842 🐞 fix vite config for production system
ref #108
2021-04-02 21:07:16 +02:00
5352410d0c 🐞 fix NGINX config
ref #108
2021-04-02 21:06:57 +02:00
c5d155396a 💾 prevent env.js from being cached
ref #108
2021-04-01 19:35:27 +02:00
93187099d3 🔨 re-added VS Code devcontainer config
ref #108
2021-04-01 19:35:10 +02:00
aa24b1dce5 📃 added readme
ref #108
2021-04-01 19:32:10 +02:00
eb3ede9593 fix dev script
ref #108
2021-04-01 19:30:31 +02:00
d7fecfbd0b version bumps
ref #108
2021-04-01 19:30:15 +02:00
b065b4ff21 📍 version bump + pin
ref #108
2021-03-30 18:36:20 +02:00
87370d24be Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-30 18:29:03 +02:00
8f8b9988ad new license file version [CI SKIP] 2021-03-30 16:29:19 +00:00
f8ccf4f5d8 🚀RELEASE v0.11.0 2021-03-30 18:28:53 +02:00
25d8b86efd Merge pull request 'Generate and print bulk blank cards feature/116-download_blanc_cards' (#117) from feature/116-download_blanc_cards into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #117
2021-03-30 16:27:48 +00:00
0cd3e937d8 bump vite to 2.1.3
ref #108
2021-03-30 18:21:18 +02:00
89bb9c215e Sorted translations
ref #116
2021-03-29 18:52:25 +02:00
2d18686ce7 Bumped lfk client js version
ref #116
2021-03-29 18:52:10 +02:00
1d999d4910 Now returning cards on creation with pdf
ref #116
2021-03-29 18:23:17 +02:00
7dfaa7579a Bumped lfk-client-js
ref #116
2021-03-29 18:15:00 +02:00
08cb079e97 Fixed button styling
ref #116
2021-03-29 17:57:34 +02:00
450aa83592 Merge branch 'feature/116-download_blanc_cards' of git.odit.services:lfk/frontend into feature/116-download_blanc_cards
# Conflicts:
#	src/components/cards/AddCardBulkModal.svelte
2021-03-29 17:47:18 +02:00
0614c76e92 Added button (including translations
ref #116
2021-03-29 17:46:56 +02:00
97e338f9d4 Added button (including translations
ref #116
2021-03-29 17:46:51 +02:00
636f018daa Added comment
ref #116
2021-03-29 17:44:59 +02:00
c8d639024a Added function for generating cards with pdf
ref #116
2021-03-29 17:44:30 +02:00
6be2ee626a package cleanup 2021-03-26 21:22:46 +01:00
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
ec447e2e36 Merge branch 'dev' into feature/94-runnercard_mgnt 2021-03-25 20:30:31 +01:00
9f7d2234fb Formatting
ref #94
2021-03-25 20:22:01 +01:00
ddd82a71a7 Moved the pdf generation related componenets to their own folder
ref #94
2021-03-25 20:20:21 +01:00
014ba3bf67 Teams now use the new sponsoring contracts module
ref #94
2021-03-25 20:17:48 +01:00
c87321f804 Fixed org generation not hiding the generation toast
ref #94
2021-03-25 20:12:32 +01:00
8b451b3c67 Orgs now use the new sponsoring contracts module
ref #94
2021-03-25 20:09:43 +01:00
0cfc87fbe6 Moved contract generation to it's own component
ref #94
2021-03-25 20:06:35 +01:00
ae9673070c Now w/ working dialog🎉🎉🎉
ref #94
2021-03-25 19:14:15 +01:00
008027db0e added windicss settings for VSCode
ref #108
2021-03-25 18:57:33 +01:00
aec5e3473e for await fix - ViteJS
ref #108
2021-03-25 18:56:18 +01:00
95c8fde72f updated default entrypoint
ref #108
2021-03-25 18:56:02 +01:00
0f32968fae 🐳 new Dockerfiles
ref #108
2021-03-25 18:55:43 +01:00
ae79e9fea1 basic ViteJS migration
ref #108
2021-03-25 18:55:24 +01:00
1a52aaf8d1 Moved modal import to overview for simplification
ref #94
2021-03-25 18:55:16 +01:00
d6c315ab8e Sorted translations 🌍
ref #94
2021-03-25 18:39:03 +01:00
983ce56048 Merge branch 'dev' into feature/94-runnercard_mgnt
# Conflicts:
#	src/locales/de.json
#	src/locales/en.json
2021-03-25 18:38:31 +01:00
de2fe0e9f1 Sorted translations
ref #94
2021-03-25 18:36:51 +01:00
fac059f02c Now w/working editing
ref #94
2021-03-24 16:58:06 +01:00
0313f8cc49 Added runnercard detail/edit modal
ref #94
2021-03-24 16:43:05 +01:00
7ad6b73574 Implemented bulk creation
ref #94
2021-03-23 19:55:55 +01:00
3cd0468b19 Bumped lfk client lib version
ref #94
2021-03-23 18:57:13 +01:00
f46ccb610e Added bulk creation modal to cards view
ref #94
2021-03-23 18:41:00 +01:00
8a32569a3b Added bulk card creation modal
ref #94
2021-03-23 18:35:21 +01:00
535b23ae91 Implemented Add card modal
ref #94
2021-03-23 17:58:13 +01:00
4715978f81 Added message for missing runner/blanco card)
ref #94
2021-03-23 17:39:14 +01:00
a516aa7775 Formatting
ref #94
2021-03-23 17:34:25 +01:00
77e9c205f9 Now importing runner overview
ref #94
2021-03-23 17:34:01 +01:00
e852305400 Now routing the cards page
ref #94
2021-03-23 17:31:11 +01:00
c6a15264b3 Added basic card overview
ref #94
2021-03-23 17:29:21 +01:00
2d0beaaaad Added CardsEmptyState + Emtystate graphic
ref #94
2021-03-23 17:19:10 +01:00
5c5ef95d2b Added basic cards page
ref #94
2021-03-23 17:13:31 +01:00
9aa8e7edff Merge pull request 'first merge to main 🚀' (#71) from dev into main
Reviewed-on: #71
Reviewed-by: Nicolai Ort <info@nicolai-ort.com>
2021-02-19 17:03:02 +00:00
49 changed files with 4440 additions and 2937 deletions

6
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM mcr.microsoft.com/vscode/devcontainers/base:alpine-3.12
RUN apk update
RUN apk add --upgrade nodejs-current npm
RUN npm i -g yarn rimraf
RUN rimraf node_modules
RUN yarn set version berry

View File

@@ -0,0 +1,20 @@
{
"name": "Node.js",
"build": {
"dockerfile": "Dockerfile"
},
"settings": {
"terminal.integrated.shell.linux": "/bin/sh"
},
"extensions": [
"dbaeumer.vscode-eslint",
"2gua.rainbow-brackets",
"christian-kohler.npm-intellisense",
"remimarsal.prettier-now",
"svelte.svelte-vscode",
"lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets",
"voorjaar.windicss-intellisense"
],
"postCreateCommand": "yarn && yarn dev --open"
}

View File

@@ -1,3 +1 @@
public/env.sample.js public/env.sample.js
public/workbox-*.js
public/workbox-*.js.map

9
.gitignore vendored
View File

@@ -1,11 +1,10 @@
node_modules node_modules
build
package-lock.json package-lock.json
yarn.lock yarn.lock
*.map *.map
public/env.js public/env.js
public/sw.js
public/index.html
public/workbox-*.js
svelte.config.js
public/index.html public/index.html
/dist
.yarn
.pnp.js
.yarnrc.yml

View File

@@ -5,7 +5,8 @@
"remimarsal.prettier-now", "remimarsal.prettier-now",
"svelte.svelte-vscode", "svelte.svelte-vscode",
"lokalise.i18n-ally", "lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets" "fivethree.vscode-svelte-snippets",
"voorjaar.windicss-intellisense"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [
"antfu.i18n-ally" "antfu.i18n-ally"

View File

@@ -1,4 +1,5 @@
{ {
"i18n-ally.localesPaths": "src/locales", "i18n-ally.localesPaths": "src/locales",
"i18n-ally.keystyle": "nested" "i18n-ally.keystyle": "nested",
"windicss.enableCodeFolding": false,
} }

View File

@@ -2,8 +2,110 @@
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.11.0](https://git.odit.services/lfk/frontend/compare/0.10.0...0.11.0)
- Merge pull request 'Generate and print bulk blank cards feature/116-download_blanc_cards' (#117) from feature/116-download_blanc_cards into dev [`25d8b86`](https://git.odit.services/lfk/frontend/commit/25d8b86efd89c442d1bf308a8743134820acfd1f)
- Added button (including translations [`0614c76`](https://git.odit.services/lfk/frontend/commit/0614c76e924b18b512bab59933a26fec07cf483d)
- Added button (including translations [`97e338f`](https://git.odit.services/lfk/frontend/commit/97e338f9d4f388596d550990457254c7fa1a3492)
- Sorted translations [`89bb9c2`](https://git.odit.services/lfk/frontend/commit/89bb9c215e356e0940678f5cabd9e38bc203040e)
- Added function for generating cards with pdf [`c8d6390`](https://git.odit.services/lfk/frontend/commit/c8d639024a5f2f72d6e30d2ce990b08bd71a5471)
- Fixed button styling [`08cb079`](https://git.odit.services/lfk/frontend/commit/08cb079e9798392e26515d559af2637e74deea97)
- Now returning cards on creation with pdf [`1d999d4`](https://git.odit.services/lfk/frontend/commit/1d999d4910acb5efa21b3f9922cdb359babff404)
- Added comment [`636f018`](https://git.odit.services/lfk/frontend/commit/636f018daa33b99468a257bfc33477e1e644d081)
- Bumped lfk client js version [`2d18686`](https://git.odit.services/lfk/frontend/commit/2d18686ce782a434ca7bd34c07c36a35b9497273)
- Bumped lfk-client-js [`7dfaa75`](https://git.odit.services/lfk/frontend/commit/7dfaa7579a22b13194fcdd1c02b4437958261472)
#### [0.10.0](https://git.odit.services/lfk/frontend/compare/0.9.1...0.10.0)
> 26 March 2021
- 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)
- 🚀RELEASE v0.10.0 [`f7fc196`](https://git.odit.services/lfk/frontend/commit/f7fc1967a50f302af1d8b668628be2f4ab2975a3)
- 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

@@ -1,18 +1,14 @@
FROM node:15.5.1-alpine3.12 FROM node:15.5.1-alpine3.12
WORKDIR /app WORKDIR /app
RUN npm i -g pnpm
COPY package.json ./ COPY package.json ./
RUN pnpm i RUN yarn
COPY package.json *.config.js workbox-config.js template-copy.js index.template.html s-config.template.js ./ COPY package.json *.config.js index.html ./
COPY src ./src COPY src ./src
COPY public ./public COPY public ./public
RUN pnpm run build RUN yarn build
# final image # final image
FROM alpine FROM alpine
COPY --from=0 /app/build /app COPY --from=0 /app/dist /app
RUN rm -rf /app/build/_dist_/components
RUN rm -rf /app/build/_dist_/locales
RUN rm -rf /app/build-manifest.json
FROM fholzer/nginx-brotli:v1.19.1 FROM fholzer/nginx-brotli:v1.19.1
COPY --from=1 /app /usr/share/nginx/html COPY --from=1 /app /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf COPY ./nginx.conf /etc/nginx/nginx.conf

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# @odit/lfk-frontend
## ✒️ Overview
This is an API client for [https://git.odit.services/lfk/backend](@lfk/backend)
- WebApp built with [Svelte](https://svelte.dev), [WindiCSS](https://windicss.org/) (to compile [TailwindCSS](https://tailwindcss.com/)) and [Vite](https://vitejs.dev).
This application is intended for use by admin users/ members only.
## 🚀 Getting Started
```
yarn
```
## Development
```
yarn dev
/
yarn dev --open
```
## Build
```
yarn build
```

View File

@@ -1,23 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="/favicon.png" /> <link rel="icon" href="/favicon.png" />
<link rel="manifest" href="/manifest.webmanifest"> <link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/lfk-logo.png"> <link rel="apple-touch-icon" href="/lfk-logo.png">
<meta name="theme-color" content="#FFFFFF"> <meta name="theme-color" content="#FFFFFF">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lauf Für Kaya! - Admin" /> <meta name="description" content="Lauf Für Kaya! - Admin" />
<title>Lauf für Kaya! - Admin</title> <title>Lauf für Kaya! - Admin</title>
__TAILWIND_INSERT__ </head>
</head>
<body>
<body> <span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.8.4-RELEASE_INFO</span>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.8.7-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 type="module" src="/src/main.js"></script>
<script defer type="module" src="/_dist_/index.js"></script> </body>
</body>
</html> </html>

View File

@@ -6,6 +6,15 @@ http {
server { server {
error_page 404 /index.html; error_page 404 /index.html;
root /usr/share/nginx/html; root /usr/share/nginx/html;
location = /index.html {
add_header Cache-Control 'no-store';
}
location = / {
add_header Cache-Control 'no-store';
}
location = /env.js {
add_header Cache-Control 'no-store';
}
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }

View File

@@ -1,45 +1,40 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "0.8.7", "version": "0.8.4",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
"dev:all": "yarn prebuild && snowpack dev", "dev": "vite",
"dev": "cross-env NODE_ENV_ODIT=development_fast node template-copy.js && yarn build:sw && snowpack dev", "build": "vite build",
"build": "yarn prebuild && snowpack build",
"prebuild": "cross-env NODE_ENV_ODIT=production node template-copy.js && yarn build:sw",
"build:sw": "workbox generateSW workbox-config.js",
"release": "release-it", "release": "release-it",
"licenses:export": "license-exporter --json -o public" "licenses:export": "license-exporter --json -o public"
}, },
"license": "CC-BY-NC-SA-4.0", "license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@odit/lfk-client-js": "0.6.4",
"csvtojson": "^2.0.10",
"gridjs": "3.3.0",
"localforage": "1.9.0",
"marked": "^2.0.1",
"svelte-focus-trap": "1.0.1",
"svelte-i18n": "3.3.7",
"svelte-select": "^3.17.0",
"tailwindcss": "2.0.3",
"tinro": "0.6.1",
"toastify-js": "1.9.3",
"validator": "13.5.2",
"xlsx": "^0.16.9"
},
"devDependencies": { "devDependencies": {
"@odit/license-exporter": "^0.0.11", "check-password-strength": "2.0.2",
"@snowpack/plugin-svelte": "3.5.2", "@odit/lfk-client-js": "0.10.1",
"auto-changelog": "^2.2.1", "@odit/license-exporter": "0.0.11",
"@sveltejs/vite-plugin-svelte": "1.0.0-next.5",
"@types/html-minifier": "4.0.0",
"auto-changelog": "2.2.1",
"autoprefixer": "10.2.5", "autoprefixer": "10.2.5",
"cross-env": "^7.0.3", "csvtojson": "2.0.10",
"postcss": "8.2.8", "gridjs": "3.4.0",
"postcss-load-config": "3.0.1", "html-minifier": "4.0.0",
"release-it": "^14.4.1", "localforage": "1.9.0",
"snowpack": "3.0.13", "marked": "2.0.1",
"svelte": "3.35.0", "release-it": "14.5.1",
"svelte-preprocess": "4.6.9", "svelte": "3.37.0",
"workbox-cli": "6.1.2" "svelte-focus-trap": "1.2.0",
"svelte-i18n": "3.3.9",
"svelte-preprocess": "4.7.0",
"svelte-select": "3.17.0",
"tailwindcss": "2.0.4",
"tinro": "0.6.1",
"toastify-js": "1.10.0",
"validator": "13.5.2",
"vite": "2.1.5",
"vite-plugin-windicss": "0.12.2",
"xlsx": "0.16.9"
}, },
"release-it": { "release-it": {
"git": { "git": {
@@ -55,7 +50,7 @@
"publish": false "publish": false
}, },
"hooks": { "hooks": {
"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.template.html && node order.js && git add src/locales" "after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.html && node order.js && git add src/locales"
} }
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
const sveltePreprocess = require('svelte-preprocess');
const preprocess = sveltePreprocess(__insert__);
module.exports = {
preprocess
};

View File

@@ -1,26 +0,0 @@
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
public: '/',
src: '/_dist_'
},
plugins: [ '@snowpack/plugin-svelte' ],
routes: [
/* Enable an SPA Fallback in development: */
{ match: 'routes', src: '.*', dest: '/index.html' }
],
packageOptions: {
/* ... */
sourceMap: false
},
devOptions: {
/* ... */
},
buildOptions: {
/* ... */
},
alias: {
/* ... */
},
optimize: { bundle: true, minify: true }
};

View File

@@ -1,5 +1,4 @@
<script> <script>
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";
@@ -53,7 +52,6 @@
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 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";
@@ -69,13 +67,12 @@
import Donations from "./components/donations/Donations.svelte"; import Donations from "./components/donations/Donations.svelte";
import DonationDetail from "./components/donations/DonationDetail.svelte"; import DonationDetail from "./components/donations/DonationDetail.svelte";
import GroupDetail from "./components/groups/GroupDetail.svelte"; import GroupDetail from "./components/groups/GroupDetail.svelte";
import ScanStationsOverview from "./components/scanstations/ScanStationsOverview.svelte";
import ScanStations from "./components/scanstations/ScanStations.svelte"; import ScanStations from "./components/scanstations/ScanStations.svelte";
import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte"; import ScanStationDetail from "./components/scanstations/ScanStationDetail.svelte";
import Scans from "./components/scans/Scans.svelte"; import Scans from "./components/scans/Scans.svelte";
import ScanDetail from "./components/scans/ScanDetail.svelte"; import ScanDetail from "./components/scans/ScanDetail.svelte";
import Cards from "./components/cards/Cards.svelte";
store.init(); store.init();
registerSW();
</script> </script>
<Route> <Route>
@@ -185,6 +182,14 @@ import ScanDetail from "./components/scans/ScanDetail.svelte";
<DonationDetail {params} /> <DonationDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/cards/*">
<Route path="/">
<Cards />
</Route>
<!-- <Route path="/:scanid" let:params>
<ScanDetail {params} />
</Route> -->
</Route>
<Route path="/scans/*"> <Route path="/scans/*">
<Route path="/"> <Route path="/">
<Scans /> <Scans />

View File

@@ -1,6 +0,0 @@
<style global>
/*! @import */
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

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

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

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddCardBulkModal from "./AddCardBulkModal.svelte";
import AddCardModal from "./AddCardModal.svelte";
import CardsOverview from "./CardsOverview.svelte";
$: current_cards = [];
export let modal_open = false;
export let bulk_modal_open = false;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('cards')}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('add-card')}
</button>
<button
on:click={() => {
bulk_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create-bulk-cards')}
</button>
{/if}
</span>
<CardsOverview bind:current_cards />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:CREATE')}
<AddCardModal bind:current_cards bind:modal_open />
<AddCardBulkModal bind:current_cards bind:bulk_modal_open />
{/if}

View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,308 +1,327 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import localForage from "localforage"; import localForage from "localforage";
import store from "../../store"; import store from "../../store";
import { router } from "tinro"; import { router } from "tinro";
import NoComponentLoaded from "../base/NoComponentLoaded.svelte"; import NoComponentLoaded from "../base/NoComponentLoaded.svelte";
import { AuthService } from "@odit/lfk-client-js"; import { AuthService } from "@odit/lfk-client-js";
$: navOpen = false; $: navOpen = false;
function logout() { function logout() {
localForage.clear(); localForage.clear();
location.replace("/"); location.replace("/");
} }
</script> </script>
<section class="min-h-screen bg-gray-50"> <section class="min-h-screen bg-gray-50">
<nav <nav
class:-translate-x-full={!navOpen} class:-translate-x-full={!navOpen}
class:translate-x-0={navOpen} class:translate-x-0={navOpen}
class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 md:translate-x-0 bg-gray-50"> class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 md:translate-x-0 bg-gray-50">
<a href="/" class="flex items-center px-4 py-5"> <a href="/" class="flex items-center px-4 py-5">
<img src="/lfk-logo.png" alt="Logo" class="h-10" /> <img src="/lfk-logo.png" alt="Logo" class="h-10" />
<h3 class="text-lg">Lauf für Kaya! Admin</h3> <h3 class="text-lg">Lauf für Kaya! Admin</h3>
</a> </a>
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation"> <nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
<a <a
class:bg-gray-100={$router.path === '/'} class:bg-gray-100={$router.path === '/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/"> href="/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" viewBox="0 0 20 20"
fill="currentColor"> fill="currentColor">
<path <path
d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" /> d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
</svg> </svg>
<span>{$_('dashboard-title')}</span> <span>{$_('dashboard-title')}</span>
</a> </a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
<a <a
class:bg-gray-100={$router.path.includes('/orgs/')} class:bg-gray-100={$router.path.includes('/orgs/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/orgs/"> href="/orgs/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg> d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg>
<span>{$_('orgs')}</span> <span>{$_('orgs')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')}
<a <a
class:bg-gray-100={$router.path === '/users/'} class:bg-gray-100={$router.path === '/users/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/users/"> href="/users/">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" /></svg> d="M12 14v8H4a8 8 0 018-8zm0-1a6 6 0 110-12 6 6 0 010 12zm2.6 5.81a3.51 3.51 0 010-1.62l-1-.57 1-1.74 1 .58a3.5 3.5 0 011.4-.82V13.5h2v1.15a3.5 3.5 0 011.4.8l1-.57 1 1.74-1 .57a3.51 3.51 0 010 1.62l1 .57-1 1.74-1-.58a3.5 3.5 0 01-1.4.82v1.14h-2v-1.15a3.5 3.5 0 01-1.4-.8l-1 .57-1-1.74 1-.57zM18 17a1 1 0 100 2 1 1 0 000-2z" /></svg>
<span>{$_('users')}</span> <span>{$_('users')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')}
<a <a
class:bg-gray-100={$router.path === '/groups/'} class:bg-gray-100={$router.path === '/groups/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/groups/"> href="/groups/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 640 512"><path
fill="currentColor" fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('user-groups')}</span> <span>{$_('user-groups')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
<a <a
class:bg-gray-100={$router.path === '/runners/'} class:bg-gray-100={$router.path === '/runners/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/runners/"> href="/runners/">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg> d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
<span>{$_('runners')}</span> <span>{$_('runners')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
<a <a
class:bg-gray-100={$router.path === '/teams/'} class:bg-gray-100={$router.path === '/teams/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/teams/"> href="/teams/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 640 512"><path
fill="currentColor" fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg> d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('teams')}</span> <span>{$_('teams')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
<a <a
class:bg-gray-100={$router.path.includes('/donors/')} class:bg-gray-100={$router.path.includes('/donors/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donors/"> href="/donors/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg> d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg>
<span>{$_('donors')}</span> <span>{$_('donors')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
<a <a
class:bg-gray-100={$router.path.includes('/donations/')} class:bg-gray-100={$router.path.includes('/donations/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/donations/"> href="/donations/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg> d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z" /></svg>
<span>{$_('donations')}</span> <span>{$_('donations')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')}
<a <a
class:bg-gray-100={$router.path === '/tracks/'} class:bg-gray-100={$router.path === '/tracks/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/tracks/"> href="/tracks/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path viewBox="0 0 640 512"><path
fill="currentColor" fill="currentColor"
d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg> d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg>
<span>{$_('tracks')}</span> <span>{$_('tracks')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
<a <a
class:bg-gray-100={$router.path === '/scans/'} class:bg-gray-100={$router.path === '/cards/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/scans/"> href="/cards/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24" height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24">
<path <path fill="none" d="M0 0h24v24H0z" />
fill="currentColor" <path
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> fill="currentColor"
<span>Scans</span> d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
</a> <span>{$_('cards')}</span>
{/if} </a>
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')} {/if}
<a {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
class:bg-gray-100={$router.path === '/contacts/'} <a
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class:bg-gray-100={$router.path === '/scans/'}
href="/contacts/"> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<svg href="/scans/">
fill="currentColor" <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 24 24" width="24"
width="24" height="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> xmlns="http://www.w3.org/2000/svg"
<path viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg> <path
<span>{$_('contacts')}</span> fill="currentColor"
</a> d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg>
{/if} <span>Scans</span>
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')} </a>
<a {/if}
class:bg-gray-100={$router.path === '/scanstations/'} {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" <a
href="/scanstations/"> class:bg-gray-100={$router.path === '/contacts/'}
<svg class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" href="/contacts/">
fill="currentColor" <svg
width="24" fill="currentColor"
height="24" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"><path viewBox="0 0 24 24"
fill="none" width="24"
d="M0 0h24v24H0z" /> height="24"><path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> <span>{$_('contacts')}</span>
<span>{$_('scanstations')}</span> </a>
</a> {/if}
{/if} {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')}
<a <a
class:bg-gray-100={$router.path === '/settings/'} class:bg-gray-100={$router.path === '/scanstations/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/settings/"> href="/scanstations/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 20 20" width="24"
fill="currentColor"> height="24"
<path viewBox="0 0 24 24"
fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg"><path
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" fill="none"
clip-rule="evenodd" /> d="M0 0h24v24H0z" />
</svg> <path
<span>{$_('settings')}</span> fill="currentColor"
</a> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
<a <span>{$_('scanstations')}</span>
class:bg-gray-100={$router.path === '/about/'} </a>
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" {/if}
href="/about/"> <a
<svg class:bg-gray-100={$router.path === '/settings/'}
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
xmlns="http://www.w3.org/2000/svg" href="/settings/">
fill="none" <svg
stroke="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
stroke-width="2" xmlns="http://www.w3.org/2000/svg"
stroke-linecap="round" viewBox="0 0 20 20"
stroke-linejoin="round" fill="currentColor">
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /> <path
<path d="M12 16v-4M12 8h.01" /></svg> fill-rule="evenodd"
<span>{$_('about')}</span> d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
</a> clip-rule="evenodd" />
<span </svg>
tabindex="0" <span>{$_('settings')}</span>
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" </a>
on:click={() => { <a
AuthService.authControllerLogout(); class:bg-gray-100={$router.path === '/about/'}
logout(); class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
}}> href="/about/">
<svg <svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor" xmlns="http://www.w3.org/2000/svg"
width="24" fill="none"
height="24" stroke="currentColor"
xmlns="http://www.w3.org/2000/svg" stroke-width="2"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> stroke-linecap="round"
<path stroke-linejoin="round"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg> viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<span>{$_('logout')}</span> <path d="M12 16v-4M12 8h.01" /></svg>
</span> <span>{$_('about')}</span>
</nav> </a>
</nav> <span
<div class="ml-0 transition md:ml-60"> tabindex="0"
<header class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
on:click={() => { on:click={() => {
navOpen = true; AuthService.authControllerLogout();
}} logout();
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"> }}>
<button class="block btn btn-light md:hidden"> <svg
<span class="sr-only">Menu</span><svg class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
class="w-4 h-4" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" width="24"
viewBox="0 0 20 20" height="24"
fill="currentcolor"><path xmlns="http://www.w3.org/2000/svg"
fill-rule="evenodd" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" <path
clip-rule="evenodd" /></svg></button> d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg>
</header> <span>{$_('logout')}</span>
<slot> </span>
<NoComponentLoaded /> </nav>
</slot> </nav>
</div> <div class="ml-0 transition md:ml-60">
<div <header
on:click={() => { on:click={() => {
navOpen = false; navOpen = true;
}} }}
class:hidden={!navOpen} class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden">
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" /> <button class="block btn btn-light md:hidden">
</section> <span class="sr-only">Menu</span><svg
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentcolor"><path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd" /></svg></button>
</header>
<slot>
<NoComponentLoaded />
</slot>
</div>
<div
on:click={() => {
navOpen = false;
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
</section>

View File

@@ -10,6 +10,9 @@
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte"; import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte"; import PromiseError from "../base/PromiseError.svelte";
import Select from "svelte-select"; import Select from "svelte-select";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import { tick } from "svelte";
$: delete_triggered = false; $: delete_triggered = false;
$: address_valid_or_none = $: address_valid_or_none =
(isAddress1Valid && iszipcodevalid && iscityvalid) || (isAddress1Valid && iszipcodevalid && iscityvalid) ||
@@ -18,6 +21,9 @@
let original = ""; let original = "";
let original_object = {}; let original_object = {};
let contacts = []; let contacts = [];
let valueCopy = null;
let areaDom;
let copied = false;
export let params; export let params;
$: editable = {}; $: editable = {};
$: contact = {}; $: contact = {};
@@ -26,7 +32,10 @@
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0; $: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0; $: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0; $: iscityvalid = editable.address?.city?.trim().length !== 0;
$: sponsoring_contracts_download_open = false; $: sponsoring_contracts_show = true;
$: cards_show = true;
$: generate_orgs = [original_object];
$: registrationLink = `${config.baseurl}/selfservice/register/${editable.registrationKey}`;
const getContactLabel = (option) => const getContactLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne( const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne(
@@ -60,14 +69,6 @@
}); });
let modal_open = false; let modal_open = false;
let delete_org = {}; let delete_org = {};
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
function deleteOrganization() { function deleteOrganization() {
RunnerOrganizationService.runnerOrganizationControllerRemove( RunnerOrganizationService.runnerOrganizationControllerRemove(
original_object.id, original_object.id,
@@ -102,6 +103,7 @@
postdata postdata
) )
.then((resp) => { .then((resp) => {
editable.registrationKey = resp.registrationKey;
original_object = Object.assign({}, editable); original_object = Object.assign({}, editable);
original = JSON.stringify(original_object); original = JSON.stringify(original_object);
Toastify({ Toastify({
@@ -114,58 +116,46 @@
} else { } else {
} }
} }
export let import_modal_open = false; async function copy() {
async function generateSponsoringContract(locale) { if(!editable.registrationKey){
sponsoring_contracts_download_open = false; Toastify({
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners( text: $_('you-have-to-save-your-changes-to-generate-a-link'),
original_object.id duration: 500,
); backgroundColor:
const toast = Toastify({ "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
text: $_("generating-pdf"), }).showToast();
duration: -1, return;
}).showToast(); }
fetch( valueCopy = registrationLink;
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, await tick();
{ areaDom.focus();
method: "POST", areaDom.select();
headers: { try {
"Content-Type": "application/json", const successful = document.execCommand("copy");
}, if (!successful) {
body: JSON.stringify(runners), throw new Error();
} }
) Toastify({
.then((response) => { text: $_("copied-link-to-clipboard"),
if (response.status != "200") { duration: 500,
toast.hideToast(); backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
Toastify({ }).showToast();
text: $_("pdf-generation-failed"), copied = true;
duration: 3500, } catch (err) {
backgroundColor: Toastify({
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", text: $_("error-whyile-copying-to-clipboard"),
}).showToast(); duration: 500,
} else { backgroundColor:
return response.blob(); "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
} }).showToast();
}) }
.then((blob) => { // we can notifi by event or storage about copy status
const url = window.URL.createObjectURL(blob); valueCopy = null;
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + original_object.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
toast.hideToast();
Toastify({
text: $_("pdf-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} }
export let import_modal_open = false;
</script> </script>
{#if valueCopy != null}<textarea bind:this={areaDom}>{valueCopy}</textarea>{/if}
<ImportRunnerModal <ImportRunnerModal
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
import_modal_open = false; import_modal_open = false;
@@ -182,64 +172,10 @@
<div class="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{original_object.name} {original_object.name}
<span data-id="org_actions_${editable.id}"> <span data-id="org_actions_${editable.id}">
<div id="sponsoring:dropdown" class="relative inline-block"> <GenerateSponsoringContracts
<div> bind:sponsoring_contracts_show
<button bind:generate_orgs />
on:click={() => { <GenerateRunnerCards bind:cards_show bind:generate_orgs />
sponsoring_contracts_download_open = !sponsoring_contracts_download_open;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contracts')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button <button
on:click={() => { on:click={() => {
@@ -382,99 +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,5 +1,6 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
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";
@@ -7,9 +8,12 @@
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";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: sponsoring_contracts_download_open = false; $: sponsoring_contracts_show = current_organizations.some((r) => r.is_selected === true);
$: cards_show = current_organizations.some((r) => r.is_selected === true);
$: generate_orgs = current_organizations.filter((r) => r.is_selected === true);
export let current_organizations = []; export let current_organizations = [];
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then( const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
@@ -17,72 +21,6 @@
current_organizations = val; current_organizations = val;
} }
); );
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
async function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
const orgs = current_organizations.filter((r) => r.is_selected === true);
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
for await (const o of orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id
);
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
count++;
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + o.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
if (count === orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
</script> </script>
<ConfirmOrgDeletion <ConfirmOrgDeletion
@@ -111,56 +49,12 @@
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="gridjs-input gridjs-search-input mb-4" />
<div class="h-12"> <div class="h-12">
{#if current_organizations.some((r) => r.is_selected === true)} <GenerateSponsoringContracts
<div id="sponsoring:dropdown" class="relative inline-block"> bind:sponsoring_contracts_show
<div> bind:generate_orgs />
<button <GenerateRunnerCards
on:click={() => { bind:cards_show
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; bind:generate_orgs />
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contracts')}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="-mr-1 ml-2 h-5 w-5"><path fill="none" d="M0 0h24v24H0z"/><path fill="currentColor" d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"/></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu">
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}
</div> </div>
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">

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

View File

@@ -0,0 +1,273 @@
<script>
import { _ } from "svelte-i18n";
import {
DonationService,
RunnerTeamService,
RunnerOrganizationService
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let certificates_show = false;
export let generate_runners = [];
export let generate_orgs = [];
export let generate_teams = [];
$: certificates_dropdown_open = false;
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "certificates:dropdown" &&
e.target.parentNode?.parentNode?.id != "certificates:dropdown:menu"
) {
certificates_dropdown_open = false;
}
});
function generateCertificates(locale) {
certificates_dropdown_open = false;
if (generate_orgs.length > 0) {
generateOrgCertificates(locale);
} else if (generate_teams.length > 0) {
generateTeamCertificates(locale);
} else {
generateRunnerCertificates(locale);
}
}
async function generateRunnerCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
const current_donations = await DonationService.donationControllerGetAll();
let certificateRunners = [];
for (let runner of generate_runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl}/documents/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.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 = "Certificates.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 generateTeamCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
const current_donations = await DonationService.donationControllerGetAll();
for (const t of generate_teams) {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
console.log(certificateRunners)
fetch(
`${config.baseurl}/documents/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.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 = "Certificates_" + 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 generateOrgCertificates(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
let count = 0;
const current_donations = await DonationService.donationControllerGetAll();
for (const o of generate_orgs) {
const runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.find((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl}/documents/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.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 = "Certificates_" + 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 certificates_show}
<div id="certificates:dropdown" class="relative inline-block">
<div>
<button
on:click={() => {
certificates_dropdown_open = !certificates_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-runner-certificates')}
<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 certificates_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="certificates: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={() => {
generateCertificates('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={() => {
generateCertificates('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,9 +7,17 @@
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";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: sponsoring_contracts_download_open = false; $: sponsoring_contracts_show = current_teams.some(
(r) => r.is_selected === true
);
$: cards_show = current_teams.some(
(r) => r.is_selected === true
);
$: generate_teams = current_teams.filter((r) => r.is_selected === true);
export let current_teams = []; export let current_teams = [];
let modal_open = false; let modal_open = false;
let delete_team = {}; let delete_team = {};
@@ -19,70 +27,6 @@
teams_promise.then((data) => { teams_promise.then((data) => {
usersstore.set(data); usersstore.set(data);
}); });
document.addEventListener("click", function (e) {
if (
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
) {
sponsoring_contracts_download_open = false;
}
});
async function generateSponsoringContract(locale) {
sponsoring_contracts_download_open = false;
const teams = current_teams.filter((r) => r.is_selected === true);
const toast = Toastify({
text: $_("generating-pdfs"),
duration: -1,
}).showToast();
let count = 0;
for await (const t of teams) {
count++;
const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
fetch(
`${config.baseurl}/documents/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "Sponsorings_" + t.name + ".pdf";
document.body.appendChild(a);
a.click();
a.remove();
if (count === teams.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
</script> </script>
<ConfirmTeamDeletion <ConfirmTeamDeletion
@@ -111,69 +55,12 @@
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="gridjs-input gridjs-search-input mb-4" />
<div class="h-12"> <div class="h-12">
{#if current_teams.some((r) => r.is_selected === true)} <GenerateSponsoringContracts
<div id="sponsoring:dropdown" class="relative inline-block"> bind:sponsoring_contracts_show
<div> bind:generate_teams />
<button <GenerateRunnerCards
on:click={() => { bind:cards_show
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; bind:generate_teams />
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
id="options-menu"
aria-haspopup="true"
aria-expanded="true">
{$_('generate-sponsoring-contracts')}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="-mr-1 ml-2 h-5 w-5"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z" /></svg>
</button>
</div>
{#if sponsoring_contracts_download_open}
<div
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"
id="sponsoring:dropdown:menu"
on:click_outside={() => {
sponsoring_contracts_download_open = false;
}}>
<div
class="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu">
<span
class="block w-full text-left px-4 py-2 text-sm text-gray-700">{$_('select-language')}</span>
<button
on:click={() => {
generateSponsoringContract('de');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('german')}
</button>
<button
on:click={() => {
generateSponsoringContract('en');
}}
type="submit"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 inline-flex"
role="menuitem">
{$_('english')}
</button>
</div>
</div>
{/if}
</div>
{/if}
</div> </div>
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">

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

@@ -1,14 +0,0 @@
import App from './App.svelte';
const app = new App({
target: document.body
});
export default app;
// HMR
if (import.meta.hot) {
import.meta.hot.accept();
import.meta.hot.dispose(() => {
app.$destroy();
});
}

View File

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

View File

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

9
src/main.js Normal file
View File

@@ -0,0 +1,9 @@
import 'windi.css';
import "toastify-js/src/toastify.css";
import "gridjs/dist/theme/mermaid.css";
import App from './App.svelte';
const app = new App({
target: document.body
});
export default app;

View File

@@ -1,14 +0,0 @@
export const register = () => {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then(
(registration) => {
// console.log(`sw successful with scope: ${registration.scope}`);
},
(err) => {
// console.log(`sw failed: ${err}`);
}
);
});
}
};

View File

@@ -1,14 +1,13 @@
module.exports = { module.exports = {
purge: {
content: [ './src/**/*.svelte' ]
},
// darkMode: 'media',
variants: {},
plugins: [],
theme: { theme: {
container: { extend: {
center: true, colors: {
padding: '1.5rem' reepolee: {
500: '#b40000',
600: '#9c0000',
700: '#750000'
}
}
} }
} }
}; };

View File

@@ -1,19 +0,0 @@
const fs = require('fs');
let content_svelteconfig = fs.readFileSync('./s-config.template.js', { encoding: 'utf8' });
let content_html = fs.readFileSync('./index.template.html', { encoding: 'utf8' });
if (process.env.NODE_ENV_ODIT == 'development_fast') {
content_html = content_html.replace(
'__TAILWIND_INSERT__',
'<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.2/dist/tailwind.min.css">'
);
content_svelteconfig = content_svelteconfig.replace('__insert__', '{postcss:{}}');
} else {
content_html = content_html.replace('__TAILWIND_INSERT__', '');
content_svelteconfig = content_svelteconfig.replace(
'__insert__',
"{postcss:{plugins:[require('tailwindcss'),require('autoprefixer')]}}"
);
}
fs.writeFileSync('./public/index.html', content_html);
fs.writeFileSync('./svelte.config.js', content_svelteconfig);
console.info('dev setup script done');

View File

@@ -1,5 +1,5 @@
const fs = require('fs'); const fs = require('fs');
const package = JSON.parse(fs.readFileSync(`./package.json`, { encoding: 'utf-8' })); const package = JSON.parse(fs.readFileSync(`./package.json`, { encoding: 'utf-8' }));
const original = fs.readFileSync(`./index.template.html`, { encoding: 'utf-8' }); const original = fs.readFileSync(`./index.html`, { encoding: 'utf-8' });
let out = original.replace(/RELEASE_INFO-(\S)+-RELEASE_INFO/gi, 'RELEASE_INFO-' + package.version + '-RELEASE_INFO'); let out = original.replace(/RELEASE_INFO-(\S)+-RELEASE_INFO/gi, 'RELEASE_INFO-' + package.version + '-RELEASE_INFO');
fs.writeFileSync(`./index.template.html`, out); fs.writeFileSync(`./index.html`, out);

50
vite.config.js Normal file
View File

@@ -0,0 +1,50 @@
import svelte from '@sveltejs/vite-plugin-svelte';
import windiCSS from 'vite-plugin-windicss';
import { minify } from 'html-minifier';
import { defineConfig } from 'vite';
//
const indexReplace = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
return minify(html, {
collapseWhitespace: true
});
}
};
};
export default defineConfig(({ command, mode }) => {
const isProduction = mode === 'production';
return {
// base: './',
build: {
polyfillDynamicImport: false,
cssCodeSplit: false,
minify: isProduction
},
plugins: [
windiCSS({
//@ts-ignore
verbose: true,
silent: false,
debug: true,
config: 'tailwind.config.js', // tailwind config file path (optional)
compile: false, // false: interpretation mode; true: compilation mode
prefix: 'windi-', // set compilation mode style prefix
globalPreflight: true, // set preflight style is global or scoped
globalUtility: true // set utility style is global or scoped
}),
svelte({
//@ts-ignore
hot: !isProduction,
emitCss: true,
extensions: [ '.md', '.svx', '.svelte' ],
preprocess: [
//
]
}),
indexReplace()
]
};
});

View File

@@ -1,9 +0,0 @@
module.exports = {
globDirectory: 'public',
globPatterns: [ '**/*.{js,ico,png,svg,html,webmanifest,txt,json}' ],
globIgnores: [ 'env.js', 'env.sample.js' ],
swDest: 'public/sw.js',
cleanupOutdatedCaches: true,
mode: 'production',
sourcemap: false
};