Compare commits

...

156 Commits

Author SHA1 Message Date
06a4428835 Merge pull request 'sync - 0.12.3' (#127) from dev into main
Reviewed-on: #127
Reviewed-by: Nicolai Ort <info@nicolai-ort.com>
2021-04-08 17:28:29 +00:00
5b4ede5e2f 🚀RELEASE v0.12.4
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-08 17:34:32 +02:00
d0ab3dda78 🚑 [HOTFIX] - drop "svelte-infinite-loading"
Some checks failed
continuous-integration/drone/push Build is failing
2021-04-08 17:34:04 +02:00
d9cf51b4bb 🚀RELEASE v0.12.3
Some checks failed
continuous-integration/drone/push Build is failing
2021-04-08 17:30:23 +02:00
aa17f24220 new license file version [CI SKIP] 2021-04-08 15:28:50 +00:00
cf60edf7d4 Merge pull request 'fix' (#126) from bugfix/125-mobile into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #126
close #125
2021-04-08 15:27:22 +00:00
ffbc243194 custom css fix for collapsed_navigation
ref #125
2021-04-07 21:44:29 +02:00
b6b07cf30c 🐞 bugfix for svelte x tailwind class names
ref #125
2021-04-07 21:35:01 +02:00
495a6b22bd almost fixed... 2021-04-07 21:28:21 +02:00
0acaffbdfa fix
ref #125
2021-04-07 20:25:04 +02:00
6043bc4517 🚀RELEASE v0.12.2
Some checks failed
continuous-integration/drone/push Build is failing
2021-04-07 20:11:21 +02:00
e6ed066e3f Merge pull request 'feature/110-virtual_list' (#124) from feature/110-virtual_list into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #124
2021-04-07 18:10:02 +00:00
ee4e8655b8 Merge branch 'dev' into feature/110-virtual_list 2021-04-07 20:09:35 +02:00
37970d2be6 pre-merge fixes
ref #110
2021-04-07 18:59:46 +02:00
1376788016 updated virtual scroll list 2021-04-07 18:38:52 +02:00
4cad86cf85 fixed height table 2021-04-07 18:19:58 +02:00
6304116edb wip on virtuallist 2021-04-06 22:16:24 +02:00
834ff8fa63 🚀RELEASE v0.12.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-06 16:47:37 +02:00
1f428a535e Merge pull request 'ImportRunnerModal Cancel Button feature/122-import_cancel' (#123) from feature/112-import_cancel into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #123
2021-04-06 14:42:37 +00:00
0c40966970 Added cancel button for the first stage of runner import
ref #112
2021-04-05 16:23:24 +02:00
9da071fe9b Escape now triggers foll modal close (including reset) instead of just hiding th modal
ref #112
2021-04-05 16:14:43 +02:00
892a04f289 🚀RELEASE v0.12.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-05 16:09:17 +02:00
27cc9727f1 Fixed package version
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-05 16:08:41 +02:00
f0738d451b Merge pull request 'Implmented certificate generation feature/119-Certificate_generation' (#120) from feature/119-Certificate_generation into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #120
2021-04-05 14:05:38 +00:00
9e6a8daf2c Sorted translations 🌍
ref #119
2021-04-05 16:04:44 +02:00
bfacfec765 The PFS Prefixes now get translated via i18n
ref #119
2021-04-05 16:04:26 +02:00
0bae5bf32b sponsoring pdf names now include their locale
ref #119
2021-04-05 15:37:12 +02:00
22b09d16d0 Cleaned up generation strings and added the schem for single runner generations for sponsoring contracts
ref #119
2021-04-05 15:36:01 +02:00
9c867e106e Cleaned up generation strings and added the schem for single runner generations for cards
ref #119
2021-04-05 15:33:55 +02:00
304f28a3c1 certificate pdf names now include their locale
ref #119
2021-04-05 15:31:52 +02:00
d65d3793de Changed the basic nameing generation for runenr certificate files
ref #119
2021-04-05 15:31:01 +02:00
3638d87bd2 Runnercard pdfs now include their locale
ref #119
2021-04-05 15:28:13 +02:00
b97a92860d Fixed wrong permissiong getting checked
ref #119
2021-04-05 15:24:42 +02:00
7c86a5eeb3 added missing/ wrong translations + formatting!
ref #119
2021-04-05 12:02:18 +02:00
d23dbaaf69 Removed useless console.log
ref #119
2021-04-03 20:05:27 +02:00
e6ffc371e1 Certificate generation from org detail
ref #119
2021-04-03 20:03:57 +02:00
3177c6eaa3 Certificate generation from org overview
ref #119
2021-04-03 20:02:52 +02:00
acd2f0519d Certificate generation from team detail
ref #119
2021-04-03 20:00:51 +02:00
18ec100c33 Certificate generation from team overview
ref #119
2021-04-03 19:59:34 +02:00
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
bbad338ced Merge pull request 'Merge Minor into main' (#113) from dev into main
Reviewed-on: #113
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-26 16:34:53 +00: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
0af2647965 🚀RELEASE v0.8.7
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-25 20:30:06 +01:00
08442154f4 Fixed listen on wrong permission🐞
Some checks failed
continuous-integration/drone/push Build is failing
2021-03-25 20:29:33 +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
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
50 changed files with 5034 additions and 4126 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,217 @@
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.12.4](https://git.odit.services/lfk/frontend/compare/0.12.3...0.12.4)
- 🚑 [HOTFIX] - drop "svelte-infinite-loading" [`d0ab3dd`](https://git.odit.services/lfk/frontend/commit/d0ab3dda78bbad2cea18a2491056530897d56607)
#### [0.12.3](https://git.odit.services/lfk/frontend/compare/0.12.2...0.12.3)
> 8 April 2021
- Merge pull request 'fix' (#126) from bugfix/125-mobile into dev [`#125`](https://git.odit.services/lfk/frontend/issues/125)
- almost fixed... [`495a6b2`](https://git.odit.services/lfk/frontend/commit/495a6b22bd8036593f390bdb862d325524cefbcc)
- 🐞 bugfix for svelte x tailwind class names [`b6b07cf`](https://git.odit.services/lfk/frontend/commit/b6b07cf30cc6533bd5dbfec1f813c16fde85634d)
- fix [`0acaffb`](https://git.odit.services/lfk/frontend/commit/0acaffbdfa359e52654a5afe2788aa59fe6f9036)
- 🚀RELEASE v0.12.3 [`d9cf51b`](https://git.odit.services/lfk/frontend/commit/d9cf51b4bbc2136594a03c5d0eeb8cb3f3440b2a)
- custom css fix for collapsed_navigation [`ffbc243`](https://git.odit.services/lfk/frontend/commit/ffbc243194c7faeb4fe61c12711a1c441c3994ef)
- new license file version [CI SKIP] [`aa17f24`](https://git.odit.services/lfk/frontend/commit/aa17f242209f7e7cecff774ace7a35b581adec1f)
#### [0.12.2](https://git.odit.services/lfk/frontend/compare/0.12.1...0.12.2)
> 7 April 2021
- 🚀RELEASE v0.12.2 [`6043bc4`](https://git.odit.services/lfk/frontend/commit/6043bc45174d51ab110b0ed10a8679d96127ab87)
- Merge pull request 'feature/110-virtual_list' (#124) from feature/110-virtual_list into dev [`e6ed066`](https://git.odit.services/lfk/frontend/commit/e6ed066e3ffabba6519f94d801d21a27819d0492)
- wip on virtuallist [`6304116`](https://git.odit.services/lfk/frontend/commit/6304116edb7f5e3c7b67c15e0b1740d34c513155)
- fixed height table [`4cad86c`](https://git.odit.services/lfk/frontend/commit/4cad86cf852468428d77103d052c6974b17c34c3)
- pre-merge fixes [`37970d2`](https://git.odit.services/lfk/frontend/commit/37970d2be6b6502701914e41e5bfe2c418438480)
- updated virtual scroll list [`1376788`](https://git.odit.services/lfk/frontend/commit/1376788016e767f006661f8c9e6747781f2dce55)
#### [0.12.1](https://git.odit.services/lfk/frontend/compare/0.12.0...0.12.1)
> 6 April 2021
- 🚀RELEASE v0.12.1 [`834ff8f`](https://git.odit.services/lfk/frontend/commit/834ff8fa63178f36dcacf931c128ba67a3e7bd1b)
- Merge pull request 'ImportRunnerModal Cancel Button feature/122-import_cancel' (#123) from feature/112-import_cancel into dev [`1f428a5`](https://git.odit.services/lfk/frontend/commit/1f428a535e3ae619cbf8db51d04255aac8dd8614)
- Added cancel button for the first stage of runner import [`0c40966`](https://git.odit.services/lfk/frontend/commit/0c409669700d3a8096cc04716154b0fdca458fe5)
- Escape now triggers foll modal close (including reset) instead of just hiding th modal [`9da071f`](https://git.odit.services/lfk/frontend/commit/9da071fe9ba067160334682bf00163e3630fe919)
#### [0.12.0](https://git.odit.services/lfk/frontend/compare/0.11.0...0.12.0)
> 5 April 2021
- Merge pull request 'feature/108_vite_migration' (#118) from feature/108_vite_migration into dev [`#108`](https://git.odit.services/lfk/frontend/issues/108)
- 🚀RELEASE v0.12.0 [`892a04f`](https://git.odit.services/lfk/frontend/commit/892a04f28930481715eb486b1ef4efeb98a6e999)
- Fixed package version [`27cc972`](https://git.odit.services/lfk/frontend/commit/27cc9727f1d02d186c3ccadb06e5b4b1b1d6202d)
- Merge pull request 'Implmented certificate generation feature/119-Certificate_generation' (#120) from feature/119-Certificate_generation into dev [`f0738d4`](https://git.odit.services/lfk/frontend/commit/f0738d451b02e4a298b5f9cb8ab0be16aed10a38)
- The PFS Prefixes now get translated via i18n [`bfacfec`](https://git.odit.services/lfk/frontend/commit/bfacfec76511cae3015f52698fdcbd80a7a15981)
- Sorted translations 🌍 [`9e6a8da`](https://git.odit.services/lfk/frontend/commit/9e6a8daf2c394cf17da532382ec7d049a0f89577)
- added missing/ wrong translations + formatting! [`7c86a5e`](https://git.odit.services/lfk/frontend/commit/7c86a5eeb370a43451d180a09a501066b023b9a0)
- Added i18n [`17f6f4e`](https://git.odit.services/lfk/frontend/commit/17f6f4e616bf57424ee12ad53b939429c02a0171)
- Added basic certificate generation component [`af63ce6`](https://git.odit.services/lfk/frontend/commit/af63ce67ae7d8f8a70706c3bd6755197908996ff)
- basic ViteJS migration [`ae79e9f`](https://git.odit.services/lfk/frontend/commit/ae79e9fea1963e977ef468e8e56f87d68916fadd)
- Implemented generation for orgs [`2e3ac15`](https://git.odit.services/lfk/frontend/commit/2e3ac154be0bf0776cd00f7d510f41ec676ae690)
- Implemented generation for teams [`2472640`](https://git.odit.services/lfk/frontend/commit/2472640755e3e41259a44127a875d00517a25842)
- updated default entrypoint [`95c8fde`](https://git.odit.services/lfk/frontend/commit/95c8fde72fca5cd5a644d51a33dc88e0b59fce92)
- ⏫📍 version bump + pin [`b065b4f`](https://git.odit.services/lfk/frontend/commit/b065b4ff218d07952fa45989e6e2ee7df13e07c1)
- 🧹 reorder + fix package [`12433f7`](https://git.odit.services/lfk/frontend/commit/12433f7c236906fe2b29848a0acaa6be1724da56)
- 🔨 re-added VS Code devcontainer config [`9318709`](https://git.odit.services/lfk/frontend/commit/93187099d32c506329b1437642aae985f2850689)
- 🐳 new Dockerfiles [`0f32968`](https://git.odit.services/lfk/frontend/commit/0f32968fae8b55a13d387918211983d0e61f85ab)
- 📃 added readme [`aa24b1d`](https://git.odit.services/lfk/frontend/commit/aa24b1dce5d6d73c8f42fc57f81b692350bf9665)
- Copy-paste fix [`f47d5e3`](https://git.odit.services/lfk/frontend/commit/f47d5e347d97ee127fa0380620138a9672665cd5)
- 🔨🔥 alpine based devcontainer with working yarn PnP [`777304f`](https://git.odit.services/lfk/frontend/commit/777304f2593df36f4e89d2ba7680add183ff062f)
- Copy-paste fix [`7488a8b`](https://git.odit.services/lfk/frontend/commit/7488a8b597a148c309e1b4499d277fed7f3bf9f4)
- You can now generate certificates from the runner overview [`bb9b779`](https://git.odit.services/lfk/frontend/commit/bb9b779cee909ab85ef52f13be0a917f1c0a9e62)
- Cleaned up generation strings and added the schem for single runner generations for cards [`9c867e1`](https://git.odit.services/lfk/frontend/commit/9c867e106edd68784e6d19743519c1952a0f0bc7)
- Changed the basic nameing generation for runenr certificate files [`d65d379`](https://git.odit.services/lfk/frontend/commit/d65d3793de869bcd6733a1bbdac378d0bc1128b3)
- ⏫ version bumps [`d7fecfb`](https://git.odit.services/lfk/frontend/commit/d7fecfbd0bc01f1cd44dea3c3837e0cc44afab12)
- Cleaned up generation strings and added the schem for single runner generations for sponsoring contracts [`22b09d1`](https://git.odit.services/lfk/frontend/commit/22b09d16d0acc2883e3448dad95ed0f4ea7c6aeb)
- Certificate generation from org overview [`3177c6e`](https://git.odit.services/lfk/frontend/commit/3177c6eaa31636ed4546f4797775a0f0a930f5d1)
- certificate pdf names now include their locale [`304f28a`](https://git.odit.services/lfk/frontend/commit/304f28a3c10bc4745aa5b7c80d7ba0e651540706)
- Runnercard pdfs now include their locale [`3638d87`](https://git.odit.services/lfk/frontend/commit/3638d87bd2ff83618eefda5af18ba19e38e3c2eb)
- 🐞 fix await for esNext [`a922776`](https://git.odit.services/lfk/frontend/commit/a9227768de29305b51d10c8a6e4fa1d39b7d998f)
- Certificate generation from team overview [`18ec100`](https://git.odit.services/lfk/frontend/commit/18ec100c33a1fbab526187e769dbae54d9db0867)
- Added certificate generation from runner overview and detail [`7b685d6`](https://git.odit.services/lfk/frontend/commit/7b685d6cad97d2f7f48c4b19bfc128e1355b70c4)
- package cleanup [`6be2ee6`](https://git.odit.services/lfk/frontend/commit/6be2ee626addaf5113b4b4821bd99a276bf4f329)
- sponsoring pdf names now include their locale [`0bae5bf`](https://git.odit.services/lfk/frontend/commit/0bae5bf32b8687057dca50cde21022ea8c3abee8)
- ✨ update licenses.json [`e99e9e0`](https://git.odit.services/lfk/frontend/commit/e99e9e07089520d5a48021e10d2af7739656641a)
- added windicss settings for VSCode [`008027d`](https://git.odit.services/lfk/frontend/commit/008027db0e2736a9bb9defd67178ab3fe580de04)
- Certificate generation from team detail [`acd2f05`](https://git.odit.services/lfk/frontend/commit/acd2f0519d62e55dad8e9c3c41e77b6967212502)
- ⚡💾 prevent env.js from being cached [`c5d1553`](https://git.odit.services/lfk/frontend/commit/c5d155396a92dfee6d592fb24a936ab521215f6d)
- for await fix - ViteJS [`aec5e34`](https://git.odit.services/lfk/frontend/commit/aec5e3473e687415fbfd69c550d9b012e1b1be43)
- Certificate generation from org detail [`e6ffc37`](https://git.odit.services/lfk/frontend/commit/e6ffc371e1ca2d4451e7dd4a3ca3c05564edb5fb)
- 🧹 drop unused dependencies [`ce50fa2`](https://git.odit.services/lfk/frontend/commit/ce50fa2a62f8ff98e8be9c66432caeebb3952019)
- 🐞 fix NGINX config [`5352410`](https://git.odit.services/lfk/frontend/commit/5352410d0c76fd14575d7beafc6a83f028062efe)
- Fixed wrong permissiong getting checked [`b97a928`](https://git.odit.services/lfk/frontend/commit/b97a92860d71eb0384170e245a67fa3ea3fd8e85)
- new license file version [CI SKIP] [`5cc4871`](https://git.odit.services/lfk/frontend/commit/5cc4871ec4be9f0af07738f6e3d44bdbe31cd25a)
- ⏫ bump @odit/lfk-client-js@0.10.1 [`8b74d6d`](https://git.odit.services/lfk/frontend/commit/8b74d6d759c8481f012c201e2ea0d12b29ddef90)
- 🔨 dev container open ⚡ [`ceb2146`](https://git.odit.services/lfk/frontend/commit/ceb2146c1b08bbe9250e4db7846e06bd89526c21)
- ⏫ version bump: vite-plugin-windicss@0.12.2 [`8d006d8`](https://git.odit.services/lfk/frontend/commit/8d006d8c74d71c43a9031d58f5a8c7fc55ed95fc)
- 🚚 move @svitejs/vite-plugin-svelte to @sveltejs/vite-plugin-svelte [`44b53da`](https://git.odit.services/lfk/frontend/commit/44b53da34516b00671b3e5060ba831e409ac3dd5)
- ⏫ upgrade vite-plugin-windicss@0.12.1 [`ab45fc1`](https://git.odit.services/lfk/frontend/commit/ab45fc144eaf14f63d86ee53c1db4eefd88f9c7f)
- 🐞 fix main.js linking [`467404b`](https://git.odit.services/lfk/frontend/commit/467404bfc87f3c08c5e227f194d71eea7cc48921)
- 🐞 fix vite config for production system [`10a011d`](https://git.odit.services/lfk/frontend/commit/10a011d8426e475105ff5d2d5cf4adca2ef7625c)
- fix dev script [`eb3ede9`](https://git.odit.services/lfk/frontend/commit/eb3ede9593e2e527df3e3a2f81c8e179bb555f51)
- ⏫ bump vite to 2.1.3 [`0cd3e93`](https://git.odit.services/lfk/frontend/commit/0cd3e937d852eeabe43eb6298bfabe20834240b2)
- Removed useless console.log [`d23dbaa`](https://git.odit.services/lfk/frontend/commit/d23dbaaf695a60fe5ebbc9945646a16b5fc45a16)
- Removed useless console log [`48cfc15`](https://git.odit.services/lfk/frontend/commit/48cfc15cfb09096db1bd5ddbe9138b1a604d581f)
#### [0.11.0](https://git.odit.services/lfk/frontend/compare/0.10.0...0.11.0)
> 30 March 2021
- 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)
- 🚀RELEASE v0.11.0 [`f8ccf4f`](https://git.odit.services/lfk/frontend/commit/f8ccf4f5d8f68ecee31430029889b8ab1ecec682)
- 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)
- new license file version [CI SKIP] [`8f8b998`](https://git.odit.services/lfk/frontend/commit/8f8b9988ad94ee9f3729a3fe6fdb4c558828d892)
- 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 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)
> 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)
#### [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)
> 25 March 2021
- 🚀RELEASE v0.8.6 [`c3c95bf`](https://git.odit.services/lfk/frontend/commit/c3c95bf2916618efe6764a33d9a42d35764d15be)
- Merge pull request 'Know Production Bugs 🐞' (#109) from bugfix/107-prod_issues into dev [`d2050b5`](https://git.odit.services/lfk/frontend/commit/d2050b5948890a6077cbb41d82d1a6a1d1106652) - Merge pull request 'Know Production Bugs 🐞' (#109) from bugfix/107-prod_issues into dev [`d2050b5`](https://git.odit.services/lfk/frontend/commit/d2050b5948890a6077cbb41d82d1a6a1d1106652)
- Errors now toast errors❌ [`17e0805`](https://git.odit.services/lfk/frontend/commit/17e0805fe64f6d181f55b81afa502ee6443ebabe) - Errors now toast errors❌ [`17e0805`](https://git.odit.services/lfk/frontend/commit/17e0805fe64f6d181f55b81afa502ee6443ebabe)
- Sorted translations 👀 [`82b1811`](https://git.odit.services/lfk/frontend/commit/82b1811971b974b686e7618b8a381e1589c168f6) - Sorted translations 👀 [`82b1811`](https://git.odit.services/lfk/frontend/commit/82b1811971b974b686e7618b8a381e1589c168f6)

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

@@ -10,14 +10,13 @@
<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.6-RELEASE_INFO</span> <span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.12.4-RELEASE_INFO</span>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script> <script src="/env.js"></script>
<script defer type="module" src="/_dist_/index.js"></script> <script type="module" src="/src/main.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.6", "version": "0.12.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.7.0",
"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.6",
"@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.1.1",
"tinro": "0.6.1",
"toastify-js": "1.10.0",
"validator": "13.5.2",
"vite": "2.1.5",
"vite-plugin-windicss": "0.12.5",
"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";
@@ -75,7 +73,6 @@
import ScanDetail from "./components/scans/ScanDetail.svelte"; import ScanDetail from "./components/scans/ScanDetail.svelte";
import Cards from "./components/cards/Cards.svelte"; import Cards from "./components/cards/Cards.svelte";
store.init(); store.init();
registerSW();
</script> </script>
<Route> <Route>

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

@@ -1,158 +1,240 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap"; import { focusTrap } from "svelte-focus-trap";
import { RunnerCardService } from "@odit/lfk-client-js"; import { RunnerCardService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let bulk_modal_open; export let bulk_modal_open;
export let current_cards; export let current_cards;
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
$: card_count = 0; $: card_count = 0;
$: is_card_count_valid = card_count > 0; $: is_card_count_valid = card_count > 0;
$: processed_last_submit = true; $: processed_last_submit = true;
$: createbtnenabled = is_card_count_valid; $: createbtnenabled = is_card_count_valid;
(() => { (() => {
document.onkeydown = (e) => { document.onkeydown = (e) => {
e = e || window.event; e = e || window.event;
if (e.key === "Escape") { if (e.key === "Escape") {
bulk_modal_open = false; bulk_modal_open = false;
} }
if (e.keyCode === 13) { if (e.keyCode === 13) {
if (createbtnenabled === true) { if (createbtnenabled === true) {
createbtnenabled = false; createbtnenabled = false;
submit(); submit_with_print();
} }
} }
}; };
})(); })();
function submit() { function submit_without_print() {
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: $_("creating-blanco-cards"), text: $_("creating-blanco-cards"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count) RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, false)
.then((result) => { .then((result) => {
bulk_modal_open = false; bulk_modal_open = false;
// //
Toastify({ Toastify({
text: $_("created-blanco-cards"), text: $_("created-blanco-cards"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) })
.catch((err) => { .catch((err) => {
// //
}) })
.finally(() => { .finally(() => {
processed_last_submit = true; processed_last_submit = true;
// //
toast.hideToast(); toast.hideToast();
}); });
} }
} }
</script>
function submit_with_print() {
{#if bulk_modal_open} if (processed_last_submit === true) {
<div processed_last_submit = false;
class="fixed z-10 inset-0 overflow-y-auto" const toast = Toastify({
use:focusTrap text: $_("creating-blanco-cards"),
use:clickOutside duration: -1,
on:click_outside={() => { }).showToast();
bulk_modal_open = false; RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
}}> .then((result) => {
<div bulk_modal_open = false;
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"> Toastify({
<div text: $_("created-blanco-cards"),
class="absolute inset-0 bg-gray-500 opacity-75" duration: 500,
data-id="modal_backdrop" /> backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
</div> }).showToast();
<span current_cards = current_cards.concat(result);
class="hidden sm:inline-block sm:align-middle sm:h-screen" const toast = Toastify({
aria-hidden="true">&#8203;</span> text: $_("generating-pdf"),
<div duration: -1,
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" }).showToast();
role="dialog" fetch(
aria-modal="true" `${config.baseurl}/documents/cards?&download=true&key=${config.documentserver_key}`,
aria-labelledby="modal-headline"> {
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> method: "POST",
<div class="sm:flex sm:items-start"> headers: {
<div "Content-Type": "application/json",
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 body: JSON.stringify(result),
class="h-6 w-6 text-blue-600" }
fill="currentColor" )
xmlns="http://www.w3.org/2000/svg" .then((response) => {
viewBox="0 0 24 24" if (response.status != "200") {
width="24" toast.hideToast();
height="24"><path fill="none" d="M0 0h24v24H0z" /> Toastify({
<path text: $_("pdf-generation-failed"),
fill="currentColor" duration: 3500,
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> backgroundColor:
</div> "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> }).showToast();
<h3 class="text-lg leading-6 font-medium text-gray-900"> } else {
{$_('create-bulk-blanco-cards')} return response.blob();
</h3> }
<div class="mt-2 mb-6"> })
<p class="text-sm text-gray-500"> .then((blob) => {
{$_('just-enter-how-many-you-want-and-the-system-will-create-them')} const url = window.URL.createObjectURL(blob);
</p> let a = document.createElement("a");
</div> a.href = url;
<div class="grid grid-cols-6 gap-6"> a.download = "Bulkcards.pdf";
<div class="col-span-6"> document.body.appendChild(a);
<label a.click();
for="amount" a.remove();
class="block text-sm font-medium text-gray-700">{$_('amount')}</label> toast.hideToast();
<div class="mt-1 flex rounded-md shadow-sm"> Toastify({
<input text: $_("pdf-successfully-generated"),
autocomplete="off" duration: 3500,
class:border-red-500={!is_card_count_valid} backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
class:focus:border-red-500={!is_card_count_valid} }).showToast();
class:focus:ring-red-500={!is_card_count_valid} })
bind:value={card_count} .catch((err) => {
type="number" console.error(err);
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" .catch((err) => {
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> .finally(() => {
</div> processed_last_submit = true;
{#if !is_card_count_valid} //
<span toast.hideToast();
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} </script>
</div>
</div> {#if bulk_modal_open}
</div> <div
</div> class="fixed z-10 inset-0 overflow-y-auto"
</div> use:focusTrap
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> use:clickOutside
<button on:click_outside={() => {
disabled={!createbtnenabled} bulk_modal_open = false;
class:opacity-50={!createbtnenabled} }}>
on:click={submit} <div
type="button" class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> <div class="fixed inset-0 transition-opacity" aria-hidden="true">
{$_('create')} <div
</button> class="absolute inset-0 bg-gray-500 opacity-75"
<button data-id="modal_backdrop" />
on:click={() => { </div>
bulk_modal_open = false; <span
}} class="hidden sm:inline-block sm:align-middle sm:h-screen"
type="button" aria-hidden="true">&#8203;</span>
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"> <div
{$_('cancel')} 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"
</button> role="dialog"
</div> aria-modal="true"
</div> aria-labelledby="modal-headline">
</div> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
</div> <div class="sm:flex sm:items-start">
{/if} <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

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

View File

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

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

View File

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

View File

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

@@ -1,308 +1,341 @@
<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"> <style>
<nav .collapsed_navigation {
class:-translate-x-full={!navOpen} transform: translateX(-100%);
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"> @media (min-width: 768px) {
<a href="/" class="flex items-center px-4 py-5"> .collapsed_navigation {
<img src="/lfk-logo.png" alt="Logo" class="h-10" /> transform: translateX(0px);
<h3 class="text-lg">Lauf für Kaya! Admin</h3> }
</a> }
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation"> </style>
<a
class:bg-gray-100={$router.path === '/'} <section class="min-h-screen bg-gray-50">
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" <div
href="/"> class:collapsed_navigation={!navOpen}
<svg 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 bg-gray-50">
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" <a href="/" class="flex items-center px-4 py-5">
xmlns="http://www.w3.org/2000/svg" <img src="/lfk-logo.png" alt="Logo" class="h-10" />
viewBox="0 0 20 20" <h3 class="text-lg">Lauf für Kaya! Admin</h3>
fill="currentColor"> </a>
<path <nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
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" /> <a
</svg> class:bg-gray-100={$router.path === '/'}
<span>{$_('dashboard-title')}</span> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
</a> href="/">
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')} <svg
<a class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
class:bg-gray-100={$router.path.includes('/orgs/')} xmlns="http://www.w3.org/2000/svg"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" viewBox="0 0 20 20"
href="/orgs/"> fill="currentColor">
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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" />
fill="currentColor" </svg>
xmlns="http://www.w3.org/2000/svg" <span>{$_('dashboard-title')}</span>
viewBox="0 0 24 24" </a>
width="24" {#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
height="24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path.includes('/orgs/')}
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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('orgs')}</span> href="/orgs/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')} fill="currentColor"
<a xmlns="http://www.w3.org/2000/svg"
class:bg-gray-100={$router.path === '/users/'} viewBox="0 0 24 24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/users/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
xmlns="http://www.w3.org/2000/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>
width="24" <span>{$_('orgs')}</span>
height="24" </a>
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" {/if}
fill="currentColor" {#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')}
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/users/'}
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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('users')}</span> href="/users/">
</a> <svg
{/if} xmlns="http://www.w3.org/2000/svg"
{#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')} width="24"
<a height="24"
class:bg-gray-100={$router.path === '/groups/'} 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" fill="currentColor"
href="/groups/"> viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
fill="currentColor" <span>{$_('users')}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes('USERGROUP:GET')}
viewBox="0 0 640 512"><path <a
fill="currentColor" class:bg-gray-100={$router.path === '/groups/'}
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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('user-groups')}</span> href="/groups/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} fill="currentColor"
<a width="24"
class:bg-gray-100={$router.path === '/runners/'} height="24"
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="/runners/"> viewBox="0 0 640 512"><path
<svg fill="currentColor"
xmlns="http://www.w3.org/2000/svg" d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
viewBox="0 0 24 24" <span>{$_('user-groups')}</span>
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" </a>
fill="currentColor" {/if}
width="24" {#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
height="24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('runners')}</span> href="/runners/">
</a> <svg
{/if} xmlns="http://www.w3.org/2000/svg"
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')} viewBox="0 0 24 24"
<a class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
class:bg-gray-100={$router.path === '/teams/'} fill="currentColor"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/teams/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
fill="currentColor" <span>{$_('runners')}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
viewBox="0 0 640 512"><path <a
fill="currentColor" class:bg-gray-100={$router.path === '/teams/'}
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="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('teams')}</span> href="/teams/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} fill="currentColor"
<a width="24"
class:bg-gray-100={$router.path.includes('/donors/')} height="24"
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="/donors/"> viewBox="0 0 640 512"><path
<svg fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
fill="currentColor" <span>{$_('teams')}</span>
xmlns="http://www.w3.org/2000/svg" </a>
viewBox="0 0 24 24" {/if}
width="24" {#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')}
height="24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path.includes('/donors/')}
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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('donors')}</span> href="/donors/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} fill="currentColor"
<a xmlns="http://www.w3.org/2000/svg"
class:bg-gray-100={$router.path.includes('/donations/')} viewBox="0 0 24 24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/donations/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
fill="currentColor" <span>{$_('donors')}</span>
xmlns="http://www.w3.org/2000/svg" </a>
viewBox="0 0 24 24" {/if}
width="24" {#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')}
height="24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path.includes('/donations/')}
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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('donations')}</span> href="/donations/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')} fill="currentColor"
<a xmlns="http://www.w3.org/2000/svg"
class:bg-gray-100={$router.path === '/tracks/'} viewBox="0 0 24 24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/tracks/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
fill="currentColor" <span>{$_('donations')}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')}
viewBox="0 0 640 512"><path <a
fill="currentColor" class:bg-gray-100={$router.path === '/tracks/'}
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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('tracks')}</span> href="/tracks/">
</a> <svg
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} fill="currentColor"
<a width="24"
class:bg-gray-100={$router.path === '/scans/'} height="24"
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="/scans/"> viewBox="0 0 640 512"><path
<svg fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
fill="currentColor" <span>{$_('tracks')}</span>
width="24" </a>
height="24" {/if}
xmlns="http://www.w3.org/2000/svg" {#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')}
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/cards/'}
fill="currentColor" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg> href="/cards/">
<span>Scans</span> <svg
</a> class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
{/if} fill="currentColor"
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')} width="24"
<a height="24"
class:bg-gray-100={$router.path === '/contacts/'} xmlns="http://www.w3.org/2000/svg"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" viewBox="0 0 24 24">
href="/contacts/"> <path fill="none" d="M0 0h24v24H0z" />
<svg <path
fill="currentColor" fill="currentColor"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z" /></svg>
xmlns="http://www.w3.org/2000/svg" <span>{$_('cards')}</span>
viewBox="0 0 24 24" </a>
width="24" {/if}
height="24"><path fill="none" d="M0 0h24v24H0z" /> {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')}
<path <a
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> class:bg-gray-100={$router.path === '/scans/'}
<span>{$_('contacts')}</span> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
</a> href="/scans/">
{/if} <svg
{#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
<a fill="currentColor"
class:bg-gray-100={$router.path === '/scanstations/'} width="24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" height="24"
href="/scanstations/"> xmlns="http://www.w3.org/2000/svg"
<svg viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" <path
fill="currentColor" fill="currentColor"
width="24" d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z" /></svg>
height="24" <span>Scans</span>
viewBox="0 0 24 24" </a>
xmlns="http://www.w3.org/2000/svg"><path {/if}
fill="none" {#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:GET')}
d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/contacts/'}
fill="currentColor" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> href="/contacts/">
<span>{$_('scanstations')}</span> <svg
</a> fill="currentColor"
{/if} class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
<a xmlns="http://www.w3.org/2000/svg"
class:bg-gray-100={$router.path === '/settings/'} viewBox="0 0 24 24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" width="24"
href="/settings/"> height="24"><path fill="none" d="M0 0h24v24H0z" />
<svg <path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" 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>
xmlns="http://www.w3.org/2000/svg" <span>{$_('contacts')}</span>
viewBox="0 0 20 20" </a>
fill="currentColor"> {/if}
<path {#if store.state.jwtinfo.userdetails.permissions.includes('STATION:GET')}
fill-rule="evenodd" <a
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" class:bg-gray-100={$router.path === '/scanstations/'}
clip-rule="evenodd" /> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
</svg> href="/scanstations/">
<span>{$_('settings')}</span> <svg
</a> class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
<a fill="currentColor"
class:bg-gray-100={$router.path === '/about/'} width="24"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" height="24"
href="/about/"> viewBox="0 0 24 24"
<svg xmlns="http://www.w3.org/2000/svg"><path
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" fill="none"
xmlns="http://www.w3.org/2000/svg" d="M0 0h24v24H0z" />
fill="none" <path
stroke="currentColor" fill="currentColor"
stroke-width="2" d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
stroke-linecap="round" <span>{$_('scanstations')}</span>
stroke-linejoin="round" </a>
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /> {/if}
<path d="M12 16v-4M12 8h.01" /></svg> <a
<span>{$_('about')}</span> class:bg-gray-100={$router.path === '/settings/'}
</a> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span href="/settings/">
tabindex="0" <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"
on:click={() => { xmlns="http://www.w3.org/2000/svg"
AuthService.authControllerLogout(); viewBox="0 0 20 20"
logout(); fill="currentColor">
}}> <path
<svg fill-rule="evenodd"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
fill="currentColor" clip-rule="evenodd" />
width="24" </svg>
height="24" <span>{$_('settings')}</span>
xmlns="http://www.w3.org/2000/svg" </a>
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" /> <a
<path class:bg-gray-100={$router.path === '/about/'}
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> class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
<span>{$_('logout')}</span> href="/about/">
</span> <svg
</nav> class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
</nav> xmlns="http://www.w3.org/2000/svg"
<div class="ml-0 transition md:ml-60"> fill="none"
<header stroke="currentColor"
on:click={() => { stroke-width="2"
navOpen = true; stroke-linecap="round"
}} stroke-linejoin="round"
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"> viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<button class="block btn btn-light md:hidden"> <path d="M12 16v-4M12 8h.01" /></svg>
<span class="sr-only">Menu</span><svg <span>{$_('about')}</span>
class="w-4 h-4" </a>
xmlns="http://www.w3.org/2000/svg" <span
viewBox="0 0 20 20" tabindex="0"
fill="currentcolor"><path class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
fill-rule="evenodd" on:click={() => {
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" AuthService.authControllerLogout();
clip-rule="evenodd" /></svg></button> logout();
</header> }}>
<slot> <svg
<NoComponentLoaded /> class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
</slot> fill="currentColor"
</div> width="24"
<div height="24"
on:click={() => { xmlns="http://www.w3.org/2000/svg"
navOpen = false; viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
}} <path
class:hidden={!navOpen} 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>
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" /> <span>{$_('logout')}</span>
</section> </span>
</nav>
</div>
<div class="ml-0 transition md:ml-60">
<header
class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden">
<button
on:click={() => {
navOpen = true;
}}
class="block btn btn-light md:hidden">
<span class="sr-only">Menu</span><svg
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>
{#if navOpen === true}
<div
on:click={() => {
navOpen = false;
console.log({ navOpen });
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
{/if}
</section>

View File

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

View File

@@ -0,0 +1,344 @@
<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')}-${locale}.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;
if(generate_runners.length == 1){
a.download = `${$_('runnercards')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
}
else{
a.download = `Runnercards-${locale}.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 = `${$_('runnercards')}_${t.name}-${locale}.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 = `${$_('runnercards')}_${o.name}-${locale}.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,277 @@
<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;
if(generate_runners.length == 1){
a.download = `${$_('certificates')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
}
else{
a.download = `${$_('certificates')}-${locale}.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);
}
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}-${locale}.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}-${locale}.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,257 @@
<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}-${locale}.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}-${locale}.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;
if(generate_runners.length == 1){
a.download = `${$_('sponsorings')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`;
}
a.download = `${$_('sponsorings')}-${locale}.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

@@ -34,7 +34,7 @@
document.onkeydown = (e) => { document.onkeydown = (e) => {
e = e || window.event; e = e || window.event;
if (e.key === "Escape") { if (e.key === "Escape") {
import_modal_open = false; cancelModal();
} }
if (e.keyCode === 13) { if (e.keyCode === 13) {
// //
@@ -281,6 +281,16 @@
bind:files bind:files
type="file" /> type="file" />
</div> </div>
<div class="overflow-hidden relative mt-4 mb-4">
<button
on:click={() => {
cancelModal();
}}
type="button"
class="w-full 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 md:ml-40 mr-0 sm:ml-0 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
{/if} {/if}
{#if json_output.length > 0} {#if json_output.length > 0}
{#if opened_from === 'OrgOverview'} {#if opened_from === 'OrgOverview'}

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

@@ -10,7 +10,7 @@
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('teams')} {$_('teams')}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:CREATE')}
<button <button
on:click={() => { on:click={() => {
modal_open = true; modal_open = true;
@@ -27,6 +27,6 @@
<TeamsOverview bind:current_teams /> <TeamsOverview bind:current_teams />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:CREATE')}
<AddTeamModal bind:current_teams bind:modal_open /> <AddTeamModal bind:current_teams bind:modal_open />
{/if} {/if}

View File

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

View File

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

View File

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

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
};