Compare commits

...

138 Commits

Author SHA1 Message Date
9111ad147c Updated group name conversion 2023-04-12 16:14:06 +02:00
333214aa8f Added distance conversion 2023-04-12 16:09:57 +02:00
c0cde02fec Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 16:06:58 +02:00
070a20a2e5 Table header i18n 2023-04-12 16:06:55 +02:00
c5e8409079 add basic table styling 2023-04-12 16:05:42 +02:00
67eff0eda9 Dsable column filter for distance 2023-04-12 16:03:53 +02:00
3de7b632e0 Moved sort onclick to header only 2023-04-12 16:03:20 +02:00
842248e4c4 Added sort 2023-04-12 16:02:00 +02:00
c5d7ec25b5 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 15:58:33 +02:00
a9a965d698 Added filterfunctions 2023-04-12 15:58:31 +02:00
dc866dd540 default to 50 rows pagination 2023-04-12 15:58:13 +02:00
89252619b1 Yeeted old datatable references 2023-04-12 15:56:53 +02:00
2699b06d7c Filter id as equals 2023-04-12 15:54:36 +02:00
fd0d45f721 debug table 2023-04-12 15:52:19 +02:00
5ecf838dd2 cleanup headers 2023-04-12 15:52:13 +02:00
45a7a90cb8 pagination size 2023-04-12 15:52:05 +02:00
cac851f2b1 fix pagination next prev 2023-04-12 15:51:46 +02:00
238082b657 getPaginationRowModel 2023-04-12 15:48:08 +02:00
aecbabe522 -> onkeyup 2023-04-12 15:43:52 +02:00
a9cdac4f74 Added filter 2023-04-12 15:40:43 +02:00
a59dbbe50e wip: pagination 2023-04-12 15:36:45 +02:00
9bec95ede8 dependency update, drop focusTrap, first tanstack experiment in RunnersOverview 2023-04-12 15:20:38 +02:00
70307a9e82 Added delete cards button
All checks were successful
continuous-integration/drone/push Build is passing
ref #161
2023-04-12 14:34:06 +02:00
ef077b4e6a Cards deleted migrations
ref #161
2023-04-12 14:32:55 +02:00
dcabed4e93 Added translations
ref #161
2023-04-12 14:30:35 +02:00
1af047f66e Moved filter function to typed version
All checks were successful
continuous-integration/drone/push Build is passing
closes #171
2023-04-12 14:16:36 +02:00
ee91748b3c Bumped pnpm to 8
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-29 19:50:52 +02:00
e5241d619b No longer switching pnpm store path
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-29 19:48:02 +02:00
d79608edbb Switched drone to pnpm cache
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-29 19:46:49 +02:00
4cbd26580e Moved docker to pnpm with cache 2023-03-29 19:45:35 +02:00
fe62ad5539 ignore pnpm store from now on
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-29 19:44:06 +02:00
eb13f038a1 Removed pnpm store 2023-03-29 19:43:16 +02:00
9505c2b030 add TODO for ScansOverview status filter
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-19 12:45:07 +01:00
008835c24f ScansOverview: add ThFilterTrack
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-19 12:38:09 +01:00
7083b3d8d2 card/ThFilterRunner: text align left 2023-03-19 12:29:23 +01:00
754931b2f6 ScansOverview: migrate to datatable
All checks were successful
continuous-integration/drone/push Build is passing
close #168
2023-03-19 12:28:27 +01:00
2dc8ffba32 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 15:05:29 +01:00
d0fe6a2e85 new license file version [CI SKIP] 2023-03-15 14:05:18 +00:00
85705b6e68 🚀RELEASE v0.17.3 2023-03-15 15:05:14 +01:00
3ea7a015a9 dependency fixes
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 15:04:55 +01:00
44329413ed set pnpm to @7 2023-03-15 15:02:54 +01:00
46db68ab22 🚀RELEASE v0.17.2 2023-03-15 14:53:48 +01:00
dc9d7f22a2 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 14:47:32 +01:00
f917018fd9 improved ThFilterGroup style 2023-03-15 14:47:27 +01:00
7b420c430d 🚀RELEASE v0.17.1 2023-03-15 14:44:26 +01:00
00359d25c1 new license file version [CI SKIP] 2023-03-15 13:43:57 +00:00
d8a3063735 Revert "package dependency fixes, bumps, lockfile update"
Some checks failed
continuous-integration/drone/push Build is failing
This reverts commit 2c73b9862d.
2023-03-15 14:43:33 +01:00
6491af19e3 🚀RELEASE v0.17.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 13:35:48 +01:00
61328d20ed new license file version [CI SKIP] 2023-03-15 12:34:41 +00:00
0a6d92a1f3 Switched license generation to cache registry and pnpm
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 13:34:14 +01:00
3a576d1073 Pinned pnpm in dockerfile, thx @philipp
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 13:29:33 +01:00
b30b98b521 Pinned ci node version 2023-03-15 13:25:59 +01:00
43d82a2af0 Fixed pnpm being called without being installed
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 13:18:07 +01:00
6a4495b813 Merge pull request 'bugfix/162-create_card_modal' (#163) from bugfix/162-create_card_modal into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #163
2023-03-15 12:15:24 +00:00
e8a0ad6647 You can now create cards from runners by searching via #runnerid
ref #162
2023-03-15 13:12:42 +01:00
92b89cc4d8 Fixed double space in label
ref #162
2023-03-15 13:09:07 +01:00
268b1b1d98 Removed unused log
ref #162
2023-03-15 13:07:49 +01:00
75bc89ca30 Fixed runners not showing up
ref #162
2023-03-15 13:06:56 +01:00
0625937068 Merge pull request 'filter by runner full names + "#<ID>"' (#160) from feature/159-cardsoverview-filter-for-runner-full-names-and-id into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #160
2023-03-15 11:20:05 +00:00
32a9074963 Wow this api is fun
ref #159
2023-03-15 12:14:39 +01:00
b869b5fd2a remodelled for early return
ref #159
2023-03-15 11:39:51 +01:00
3a3e2f7157 add ThFilterRunner
ref #159
2023-03-15 11:10:08 +01:00
bea57aa03a fix styling for table filters th border
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-15 11:08:05 +01:00
30991d5364 UsersOverview: drop pfp
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:53:16 +01:00
5cc8b0811c UsersOverview: change profilepic scaling
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:51:40 +01:00
2c73b9862d package dependency fixes, bumps, lockfile update
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:51:23 +01:00
732b2f061e wip: pnpm + node version 2023-03-14 09:29:27 +01:00
3680533eef 🚀RELEASE v0.16.5
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-14 09:25:11 +01:00
1307d72c9d Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-14 09:24:54 +01:00
405dfa0c34 new license file version [CI SKIP] 2023-03-14 08:24:37 +00:00
5c2d154ad1 🚀RELEASE v0.16.4 2023-03-14 09:24:31 +01:00
f2bf8d9bac fix: OrgDetail: clicking on address will toggle selfservice
All checks were successful
continuous-integration/drone/push Build is passing
close #158
2023-03-14 09:23:55 +01:00
f9cfd6bd06 🚀RELEASE v0.16.3
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 14:01:35 +01:00
287f63fa52 Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 14:00:20 +01:00
5fe47634e8 Bumped vite build targets 2023-02-23 13:59:27 +01:00
a6590910cf new license file version [CI SKIP] 2023-02-23 12:54:38 +00:00
ad454c386c Merge pull request 'feature/156-pdf_names' (#157) from feature/156-pdf_names into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #157
2023-02-23 12:54:04 +00:00
0b2c296de0 Added ids for generated pdfs
ref #156
2023-02-23 09:59:07 +01:00
0e85940cba 🚀RELEASE v0.16.2
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 08:36:54 +01:00
8d479c32f8 Merge pull request 'feature/147-cardoverview_performance' (#153) from feature/147-cardoverview_performance into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #153
2023-02-23 07:34:46 +00:00
549785cf7d Merge pull request 'Fixed scanmodal' (#155) from bugfix/154-scan_select_runner_by_id into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #155
2023-02-23 07:34:39 +00:00
aafc4c8d62 Removed unused console log
ref #154
2023-02-22 18:39:41 +01:00
47dedbdc73 Fixed scanmodal
fixes #154
2023-02-22 18:38:21 +01:00
6fe134afc8 i18n import
ref #147
2023-02-22 18:33:42 +01:00
63a50f92e7 Merge branch 'feature/147-cardoverview_performance' of git.odit.services:lfk/frontend into feature/147-cardoverview_performance 2023-02-22 18:32:29 +01:00
ca6da15ef7 i18n
ref #147
2023-02-22 18:32:25 +01:00
8dfa19fa0f Fixed all filter
ref #147
2023-02-22 18:31:36 +01:00
0feee0ae2f Fixed edit update bug
ref #147
2023-02-22 18:06:53 +01:00
2a6a39916a rename: ThFilterGroup -> ThFilterStatus
ref #147
2023-02-19 15:14:09 +01:00
f0a2b2859f Added custom status filter 2023-02-18 19:27:13 +01:00
32ddb66fc8 Trigger edit modal 2023-02-18 19:24:28 +01:00
df63c2388d Added old formatting for runner and status
ref #147
2023-02-18 19:22:42 +01:00
757655ea63 Bsic datatable conversion
ref #147
2023-02-18 19:20:29 +01:00
329c1cc037 Merge pull request 'fix: RunnerDetail: set .phone to null if empty' (#152) from bugfix/151-runnerdetail--cannot-unset-phone-number into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #152
2023-02-18 16:37:56 +00:00
da6dd55d13 set .phone to null if empty 2023-02-18 17:30:01 +01:00
0e5490f1c8 new license file version [CI SKIP] 2023-02-18 16:18:28 +00:00
b82d638de1 Merge pull request 'feature/146-runner-table-performance-data-table' (#150) from feature/146-runner-table-performance-data-table into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #150
2023-02-18 16:17:51 +00:00
224034dcc6 fix: z-index on action buttons 2023-02-18 17:17:04 +01:00
026d3d41c1 new license file version [CI SKIP] 2023-02-18 16:13:06 +00:00
fd1a06b359 Merge pull request 'feature/148-dashboard_statscards' (#149) from feature/148-dashboard_statscards into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #149
2023-02-18 16:12:30 +00:00
452d010183 Updated breakpoints
ref #148
2023-02-18 16:27:58 +01:00
eb1c17e3ac Dasboard Cards redesign 2023-02-18 16:24:13 +01:00
a101873eb0 Tailwind bump
ref #148
2023-02-18 16:24:02 +01:00
3d2acb692a Fixed top checkbox state 2023-02-18 15:53:47 +01:00
0900c2691e Fixed checkbox show 2023-02-18 15:52:59 +01:00
1337676e08 Basic checkbox fix 2023-02-18 15:49:24 +01:00
2e075eafab RunnersOverview loading fix
ref #146
2023-02-16 15:43:11 +01:00
14d64b6070 add group filtering to table
ref #146
2023-02-16 15:05:41 +01:00
81b8fbf4e3 1st datatable try with @vincjo/datatables 2023-02-16 12:27:45 +01:00
24d074752f formatting 2023-02-16 11:48:30 +01:00
08047a9307 cleaned up table search 2023-02-16 11:47:59 +01:00
1b0cd5b90b improved runner search 2023-02-16 11:45:38 +01:00
65e8998894 set table-layout:fixed + display when loaded
ref #146
2023-02-16 11:26:37 +01:00
449948050b 🚀RELEASE v0.16.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-15 15:05:33 +01:00
cf97281592 fix: donor detail: sponsorings: unset middlename will show as "null"
All checks were successful
continuous-integration/drone/push Build is passing
close #145
2023-02-15 15:05:01 +01:00
75684efa1a 🚀RELEASE v0.16.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-03 14:07:27 +01:00
2c4f27a943 new license file version [CI SKIP] 2023-02-03 13:05:38 +00:00
53b7dec7cd Merge pull request 'feature/143-beamershow_clients' (#144) from feature/143-beamershow_clients into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #144
2023-02-03 13:05:01 +00:00
e0cbfb000b Tailwind bump 2023-02-02 17:27:18 +01:00
3a66f4c862 new license file version [CI SKIP] 2023-02-02 16:23:56 +00:00
85ceaa464f Merge branch 'dev' into feature/143-beamershow_clients 2023-02-02 17:23:33 +01:00
976755338b Updated docker base images
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-02 17:21:58 +01:00
1c980059cf Switched drone to kaniko 2023-02-02 17:20:01 +01:00
2d8c4c1698 Ignore pnpm lock 2023-02-02 17:19:51 +01:00
19a333d7bd Added missing translation
ref #143
2023-02-02 17:16:24 +01:00
96c55db63d Added translation
ref #143
2023-02-02 17:13:16 +01:00
fecb07ee37 Re-added copy modal
ref #143
2023-02-02 17:11:46 +01:00
e10c6480a5 Removed Key after creation
ref #143
2023-02-02 17:08:29 +01:00
f3cc07c009 Fixed imports and naming 2023-02-02 17:05:34 +01:00
068076dd47 Added Statsclients to sidebar
ref #143
2023-02-02 17:03:18 +01:00
02158605be Basic statsclient detail
ref #143
2023-02-02 17:00:49 +01:00
674e6a90ec Updated mounted variables 2023-02-02 16:54:37 +01:00
f679330466 Updated Add modal
ref #143
2023-02-02 16:51:39 +01:00
93fc7c2e83 Updated deletion modal
ref #143
2023-02-02 16:46:18 +01:00
f299617c60 First page for statsclients
ref #143
2023-02-02 16:42:17 +01:00
28cbc5b98c Bumped apiclient
ref #143
2023-02-02 16:21:11 +01:00
c28f1ee0bc Bumped apiclient
ref #143
2023-02-02 16:04:21 +01:00
cff112d705 Pinned versions
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-02 16:02:52 +01:00
65 changed files with 8237 additions and 2903 deletions

View File

@@ -1 +1,2 @@
public/env.sample.js public/env.sample.js
.pnpm-store

View File

@@ -19,6 +19,13 @@ get:
path: odit-git-bot path: odit-git-bot
name: sshkey name: sshkey
---
kind: secret
name: npm_url
get:
path: odit-npm-cache
name: url
--- ---
kind: pipeline kind: pipeline
type: kubernetes type: kubernetes
@@ -27,10 +34,14 @@ name: build:dev
steps: steps:
- name: run full license export - name: run full license export
depends_on: ["clone"] depends_on: ["clone"]
image: registry.odit.services/hub/library/node:alpine image: registry.odit.services/hub/library/node:19.7.0-alpine3.16
commands: commands:
- yarn - npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8
- yarn licenses:export - pnpm i
- pnpm licenses:export
environment:
NPM_REGISTRY_URL:
from_secret: npm_url
- name: push new licenses file to repo - name: push new licenses file to repo
depends_on: ["run full license export"] depends_on: ["run full license export"]
image: appleboy/drone-git-push image: appleboy/drone-git-push
@@ -43,18 +54,21 @@ steps:
ssh_key: ssh_key:
from_secret: git_ssh from_secret: git_ssh
- name: build dev - name: build dev
image: plugins/docker depends_on: ["clone"]
depends_on: [clone] image: registry.odit.services/library/drone-kaniko
settings: settings:
username: username:
from_secret: docker_username from_secret: docker_username
password: password:
from_secret: docker_password from_secret: docker_password
repo: registry.odit.services/lfk/frontend build_args:
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/frontend
tags: tags:
- dev - dev
cache: true
registry: registry.odit.services registry: registry.odit.services
mtu: 1000
trigger: trigger:
branch: branch:
- dev - dev
@@ -67,18 +81,21 @@ type: kubernetes
name: build:tags name: build:tags
steps: steps:
- name: build $DRONE_TAG - name: build $DRONE_TAG
image: plugins/docker depends_on: ["clone"]
depends_on: [clone] image: registry.odit.services/library/drone-kaniko
settings: settings:
username: username:
from_secret: docker_username from_secret: docker_username
password: password:
from_secret: docker_password from_secret: docker_password
repo: registry.odit.services/lfk/frontend build_args:
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/frontend
tags: tags:
- '${DRONE_TAG}' - "${DRONE_TAG}"
cache: true
registry: registry.odit.services registry: registry.odit.services
mtu: 1000
trigger: trigger:
event: event:
- tag - tag

6
.gitignore vendored
View File

@@ -1,10 +1,6 @@
node_modules node_modules
package-lock.json
yarn.lock
*.map *.map
public/env.js public/env.js
public/index.html public/index.html
/dist /dist
.yarn .pnpm-store
.pnp.js
.yarnrc.yml

View File

@@ -2,8 +2,153 @@
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.17.3](https://git.odit.services/lfk/frontend/compare/0.17.2...0.17.3)
- dependency fixes [`3ea7a01`](https://git.odit.services/lfk/frontend/commit/3ea7a015a9beba3c2e4d3eb966f24ff6d4ac786e)
- set pnpm to @7 [`4432941`](https://git.odit.services/lfk/frontend/commit/44329413ed2ca23f74e86db041b2c25b2b1c2a2b)
#### [0.17.2](https://git.odit.services/lfk/frontend/compare/0.17.1...0.17.2)
> 15 March 2023
- new license file version [CI SKIP] [`00359d2`](https://git.odit.services/lfk/frontend/commit/00359d25c1bd3efdd6365bf284b3c07634049399)
- 🚀RELEASE v0.17.2 [`46db68a`](https://git.odit.services/lfk/frontend/commit/46db68ab229dc740dfff8835ef916f2c2e629b27)
- improved ThFilterGroup style [`f917018`](https://git.odit.services/lfk/frontend/commit/f917018fd92a8a5b034f735ac8b6e41995044317)
#### [0.17.1](https://git.odit.services/lfk/frontend/compare/0.17.0...0.17.1)
> 15 March 2023
- Revert "package dependency fixes, bumps, lockfile update" [`d8a3063`](https://git.odit.services/lfk/frontend/commit/d8a30637351e164599e07a6473d9a1d2b08d245d)
- 🚀RELEASE v0.17.1 [`7b420c4`](https://git.odit.services/lfk/frontend/commit/7b420c430d27bf0fc85a4297780164a593fc1be3)
#### [0.17.0](https://git.odit.services/lfk/frontend/compare/0.16.5...0.17.0)
> 15 March 2023
- new license file version [CI SKIP] [`61328d2`](https://git.odit.services/lfk/frontend/commit/61328d20ed2cfd1df7d3c32767f9c64154879d6d)
- wip: pnpm + node version [`732b2f0`](https://git.odit.services/lfk/frontend/commit/732b2f061e465bd08cf34c58d8cd6b021ba25dce)
- package dependency fixes, bumps, lockfile update [`2c73b98`](https://git.odit.services/lfk/frontend/commit/2c73b9862d401dac15021eed3f7847d46132a8ed)
- Fixed runners not showing up [`75bc89c`](https://git.odit.services/lfk/frontend/commit/75bc89ca3020c48f490c7602374616bd9461e78f)
- add ThFilterRunner [`3a3e2f7`](https://git.odit.services/lfk/frontend/commit/3a3e2f71575d3a0e39a5e13b05cff932b8683ac9)
- fix styling for table filters th border [`bea57aa`](https://git.odit.services/lfk/frontend/commit/bea57aa03acaaaa4b1860b30228dd5d1298317f8)
- You can now create cards from runners by searching via #runnerid [`e8a0ad6`](https://git.odit.services/lfk/frontend/commit/e8a0ad6647ab39252865f62b755f27c34ac2d243)
- remodelled for early return [`b869b5f`](https://git.odit.services/lfk/frontend/commit/b869b5fd2a01955fb21f936fa38eb5a9648e7de3)
- 🚀RELEASE v0.17.0 [`6491af1`](https://git.odit.services/lfk/frontend/commit/6491af19e375cbeba7ddd94e463b4dfe308a70a8)
- Wow this api is fun [`32a9074`](https://git.odit.services/lfk/frontend/commit/32a9074963cce3328f14b1f981ddd5ee49df0008)
- Fixed double space in label [`92b89cc`](https://git.odit.services/lfk/frontend/commit/92b89cc4d88c9d5625c2ddf7c81c98494f7f5271)
- UsersOverview: drop pfp [`30991d5`](https://git.odit.services/lfk/frontend/commit/30991d5364a09d517b2115a7e9ea3fbf1fe2e57d)
- Switched license generation to cache registry and pnpm [`0a6d92a`](https://git.odit.services/lfk/frontend/commit/0a6d92a1f3c5562f11562c433b3a04e3eaae3da4)
- Pinned pnpm in dockerfile, thx @philipp [`3a576d1`](https://git.odit.services/lfk/frontend/commit/3a576d1073ee503b68100e01054a1756bab62805)
- Pinned ci node version [`b30b98b`](https://git.odit.services/lfk/frontend/commit/b30b98b521eda2bc7fc055097546f716e90d92ef)
- Fixed pnpm being called without being installed [`43d82a2`](https://git.odit.services/lfk/frontend/commit/43d82a2af04af49c2169f78a0d0f27ef7e4d7558)
- Merge pull request 'bugfix/162-create_card_modal' (#163) from bugfix/162-create_card_modal into dev [`6a4495b`](https://git.odit.services/lfk/frontend/commit/6a4495b8131a31cd48a608c2275e80494d0a0fb4)
- Removed unused log [`268b1b1`](https://git.odit.services/lfk/frontend/commit/268b1b1d9830de196d1d95345d7a2467bbf19eb6)
- Merge pull request 'filter by runner full names + "#&lt;ID&gt;"' (#160) from feature/159-cardsoverview-filter-for-runner-full-names-and-id into dev [`0625937`](https://git.odit.services/lfk/frontend/commit/0625937068f0786078ffd29b9c8bb54949350b6c)
- UsersOverview: change profilepic scaling [`5cc8b08`](https://git.odit.services/lfk/frontend/commit/5cc8b0811cf290f97a4399b23c5ea4d961a5a91c)
#### [0.16.5](https://git.odit.services/lfk/frontend/compare/0.16.4...0.16.5)
> 14 March 2023
- 🚀RELEASE v0.16.5 [`3680533`](https://git.odit.services/lfk/frontend/commit/3680533eefef042fc77246dd3d374aafe10c428f)
- new license file version [CI SKIP] [`405dfa0`](https://git.odit.services/lfk/frontend/commit/405dfa0c34ba87fc450c22e0e9974f92c4cdeffe)
#### [0.16.4](https://git.odit.services/lfk/frontend/compare/0.16.3...0.16.4)
> 14 March 2023
- fix: OrgDetail: clicking on address will toggle selfservice [`#158`](https://git.odit.services/lfk/frontend/issues/158)
- 🚀RELEASE v0.16.4 [`5c2d154`](https://git.odit.services/lfk/frontend/commit/5c2d154ad180ce7916605871c63e2f5ac4428250)
#### [0.16.3](https://git.odit.services/lfk/frontend/compare/0.16.2...0.16.3)
> 23 February 2023
- 🚀RELEASE v0.16.3 [`f9cfd6b`](https://git.odit.services/lfk/frontend/commit/f9cfd6bd063b01a584774854d8fb5eab96f99528)
- Bumped vite build targets [`5fe4763`](https://git.odit.services/lfk/frontend/commit/5fe47634e8980e77b65c05f213c475cf49273609)
- new license file version [CI SKIP] [`a659091`](https://git.odit.services/lfk/frontend/commit/a6590910cfdc5e91fba91c1bc9237e407ef15fd2)
- Merge pull request 'feature/156-pdf_names' (#157) from feature/156-pdf_names into dev [`ad454c3`](https://git.odit.services/lfk/frontend/commit/ad454c386cbf11abc59d41d269d1a0ef7442c9ed)
- Added ids for generated pdfs [`0b2c296`](https://git.odit.services/lfk/frontend/commit/0b2c296de069a4ef302c5535de01bc18236675bc)
#### [0.16.2](https://git.odit.services/lfk/frontend/compare/0.16.1...0.16.2)
> 23 February 2023
- Fixed scanmodal [`#154`](https://git.odit.services/lfk/frontend/issues/154)
- 🚀RELEASE v0.16.2 [`0e85940`](https://git.odit.services/lfk/frontend/commit/0e85940cba292cbccd1ec038aa24f4a719382c19)
- Merge pull request 'feature/147-cardoverview_performance' (#153) from feature/147-cardoverview_performance into dev [`8d479c3`](https://git.odit.services/lfk/frontend/commit/8d479c32f82938904aee6a10deb80fea85487e4b)
- Merge pull request 'Fixed scanmodal' (#155) from bugfix/154-scan_select_runner_by_id into dev [`549785c`](https://git.odit.services/lfk/frontend/commit/549785cf7d1c9edc1be37dc745b97096232a08ca)
- i18n [`ca6da15`](https://git.odit.services/lfk/frontend/commit/ca6da15ef761184a55b18d56f749f660a32cbb83)
- Bsic datatable conversion [`757655e`](https://git.odit.services/lfk/frontend/commit/757655ea63b3667bc4612ae1595eb52910b61137)
- 1st datatable try with @vincjo/datatables [`81b8fbf`](https://git.odit.services/lfk/frontend/commit/81b8fbf4e341e6f2998a6e9e2053972c5c225021)
- Dasboard Cards redesign [`eb1c17e`](https://git.odit.services/lfk/frontend/commit/eb1c17e3ac7e8f5e7310a90421fc9db3ed15c497)
- set .phone to null if empty [`da6dd55`](https://git.odit.services/lfk/frontend/commit/da6dd55d139a672fa50204eabdca67d9740614a0)
- add group filtering to table [`14d64b6`](https://git.odit.services/lfk/frontend/commit/14d64b6070d98e6368da5709e9ff8221e8a621c7)
- formatting [`24d0747`](https://git.odit.services/lfk/frontend/commit/24d074752f1c5dc1a14b075ac14b448d7e129376)
- RunnersOverview loading fix [`2e075ea`](https://git.odit.services/lfk/frontend/commit/2e075eafab5c4d78fd9aa9d66834b477b2685bfc)
- Added old formatting for runner and status [`df63c23`](https://git.odit.services/lfk/frontend/commit/df63c2388da359dec9ed9968bc9f970be7092a45)
- Added custom status filter [`f0a2b28`](https://git.odit.services/lfk/frontend/commit/f0a2b2859fa18426a454b7d9d6dd22dfdfe7ce27)
- Basic checkbox fix [`1337676`](https://git.odit.services/lfk/frontend/commit/1337676e0894c46da0b6dcb7553e5ea8f88d0c14)
- Fixed all filter [`8dfa19f`](https://git.odit.services/lfk/frontend/commit/8dfa19fa0f9897c61342ece956df88731c7aa861)
- improved runner search [`1b0cd5b`](https://git.odit.services/lfk/frontend/commit/1b0cd5b90bcceb92627c6b7cdcdd7792ed81b50f)
- set table-layout:fixed + display when loaded [`65e8998`](https://git.odit.services/lfk/frontend/commit/65e89988943807c1606a8b6aea49564b13d52537)
- Trigger edit modal [`32ddb66`](https://git.odit.services/lfk/frontend/commit/32ddb66fc8d8cd689f1104759812f4cee4b7a613)
- cleaned up table search [`08047a9`](https://git.odit.services/lfk/frontend/commit/08047a93073c32f5dd7a8e958400ae8a5b7f4035)
- Fixed edit update bug [`0feee0a`](https://git.odit.services/lfk/frontend/commit/0feee0ae2fb6d8dba0b6fd72cedc0712dc749511)
- fix: z-index on action buttons [`224034d`](https://git.odit.services/lfk/frontend/commit/224034dcc6263d3b0a8ea20045e435142d8ed2af)
- rename: ThFilterGroup -&gt; ThFilterStatus [`2a6a399`](https://git.odit.services/lfk/frontend/commit/2a6a39916a03c0466e63354e9f5ad7924cb59b6b)
- new license file version [CI SKIP] [`0e5490f`](https://git.odit.services/lfk/frontend/commit/0e5490f1c84217a5a6d5c8745c4667b32ca65e1a)
- new license file version [CI SKIP] [`026d3d4`](https://git.odit.services/lfk/frontend/commit/026d3d41c1b976a4dc7c733576a6a9e8d4b13b78)
- Updated breakpoints [`452d010`](https://git.odit.services/lfk/frontend/commit/452d0101838d72bff7d588a953faae028e2ff819)
- Tailwind bump [`a101873`](https://git.odit.services/lfk/frontend/commit/a101873eb0946b284a11a5081642711f5087da14)
- Fixed checkbox show [`0900c26`](https://git.odit.services/lfk/frontend/commit/0900c2691e4cfe5046e8ae186c8ac8884c90abd6)
- Removed unused console log [`aafc4c8`](https://git.odit.services/lfk/frontend/commit/aafc4c8d62a7a0a493c8bd60149f90c842534bdd)
- i18n import [`6fe134a`](https://git.odit.services/lfk/frontend/commit/6fe134afc8bfef4e7470b7e53b9312b172a7322b)
- Merge pull request 'fix: RunnerDetail: set .phone to null if empty' (#152) from bugfix/151-runnerdetail--cannot-unset-phone-number into dev [`329c1cc`](https://git.odit.services/lfk/frontend/commit/329c1cc037a43c818ba3b6c72581d29586d76232)
- Merge pull request 'feature/146-runner-table-performance-data-table' (#150) from feature/146-runner-table-performance-data-table into dev [`b82d638`](https://git.odit.services/lfk/frontend/commit/b82d638de1aa1f72aada212cf3e4147d808b4fcf)
- Merge pull request 'feature/148-dashboard_statscards' (#149) from feature/148-dashboard_statscards into dev [`fd1a06b`](https://git.odit.services/lfk/frontend/commit/fd1a06b3595b3713ad474e623c74105125602d46)
- Fixed top checkbox state [`3d2acb6`](https://git.odit.services/lfk/frontend/commit/3d2acb692a28c116790248679e238fb562b24ac5)
#### [0.16.1](https://git.odit.services/lfk/frontend/compare/0.16.0...0.16.1)
> 15 February 2023
- fix: donor detail: sponsorings: unset middlename will show as "null" [`#145`](https://git.odit.services/lfk/frontend/issues/145)
- 🚀RELEASE v0.16.1 [`4499480`](https://git.odit.services/lfk/frontend/commit/449948050b8673d43a8dfbb225c3198e4bbb3c7b)
#### [0.16.0](https://git.odit.services/lfk/frontend/compare/0.15.6...0.16.0)
> 3 February 2023
- First page for statsclients [`f299617`](https://git.odit.services/lfk/frontend/commit/f299617c600d2bba7b4405c7c3acae9fd93aefa8)
- 🚀RELEASE v0.16.0 [`75684ef`](https://git.odit.services/lfk/frontend/commit/75684efa1ae0edb4b4d414757c5acf2a77c572e5)
- Basic statsclient detail [`0215860`](https://git.odit.services/lfk/frontend/commit/02158605be824e5ac21a6284731138190988c794)
- Updated Add modal [`f679330`](https://git.odit.services/lfk/frontend/commit/f679330466205e6480cd7f2b7c2b4fdc41c51525)
- Switched drone to kaniko [`1c98005`](https://git.odit.services/lfk/frontend/commit/1c980059cff5c87c452428b53513507c2339451f)
- Re-added copy modal [`fecb07e`](https://git.odit.services/lfk/frontend/commit/fecb07ee373dcaaeaea69fdf8d4c6ee2c257c89c)
- Added Statsclients to sidebar [`068076d`](https://git.odit.services/lfk/frontend/commit/068076dd47373c673a25e730cb8a57c686682810)
- Fixed imports and naming [`f3cc07c`](https://git.odit.services/lfk/frontend/commit/f3cc07c009ed0a34e61f1aad47a1a31778145439)
- new license file version [CI SKIP] [`2c4f27a`](https://git.odit.services/lfk/frontend/commit/2c4f27a943bb35be6728bb49bd5c2263cba78165)
- Merge pull request 'feature/143-beamershow_clients' (#144) from feature/143-beamershow_clients into dev [`53b7dec`](https://git.odit.services/lfk/frontend/commit/53b7dec7cd516c908d45591b855f4be09371f9b1)
- Updated deletion modal [`93fc7c2`](https://git.odit.services/lfk/frontend/commit/93fc7c2e83f78dd88f15d9246127bb9e69f1a8ee)
- Updated mounted variables [`674e6a9`](https://git.odit.services/lfk/frontend/commit/674e6a90ec23dde9377bea64c14a50e41ffa450d)
- Removed Key after creation [`e10c648`](https://git.odit.services/lfk/frontend/commit/e10c6480a504338b21e30fdf2577e5b6c3b635db)
- Updated docker base images [`9767553`](https://git.odit.services/lfk/frontend/commit/976755338b8621064f9a73147aa600af1f77cd51)
- Added translation [`96c55db`](https://git.odit.services/lfk/frontend/commit/96c55db63dbfed92b78ff0e7bdab7a8cce4d76e9)
- Pinned versions [`cff112d`](https://git.odit.services/lfk/frontend/commit/cff112d705a74a135286943298f3f344341325ac)
- Tailwind bump [`e0cbfb0`](https://git.odit.services/lfk/frontend/commit/e0cbfb000bee59a71e06bd58a9c7ef6a0fc7091d)
- Added missing translation [`19a333d`](https://git.odit.services/lfk/frontend/commit/19a333d7bda525fbcb3c68f3cbf85a4f925a9707)
- Bumped apiclient [`c28f1ee`](https://git.odit.services/lfk/frontend/commit/c28f1ee0bc4456595c21858f38e52ed6f16871c5)
- new license file version [CI SKIP] [`3a66f4c`](https://git.odit.services/lfk/frontend/commit/3a66f4c862db9f35c223cc7007b0560fef4e1d63)
- Bumped apiclient [`28cbc5b`](https://git.odit.services/lfk/frontend/commit/28cbc5b98ca09657100e1740b83aa2617243b26b)
- Ignore pnpm lock [`2d8c4c1`](https://git.odit.services/lfk/frontend/commit/2d8c4c1698a1675f618e85e678012f310f87c6ee)
#### [0.15.6](https://git.odit.services/lfk/frontend/compare/0.15.5...0.15.6) #### [0.15.6](https://git.odit.services/lfk/frontend/compare/0.15.5...0.15.6)
> 19 July 2021
- 🚀RELEASE v0.15.6 [`9fc4ad6`](https://git.odit.services/lfk/frontend/commit/9fc4ad63c4f77b46d645e83c94b51747b91247b8)
- Fixed donations getting reduced to the first one on certificates [`2391668`](https://git.odit.services/lfk/frontend/commit/2391668a25a1e11a1409df572d77ad1635070fbc) - Fixed donations getting reduced to the first one on certificates [`2391668`](https://git.odit.services/lfk/frontend/commit/2391668a25a1e11a1409df572d77ad1635070fbc)
- new license file version [CI SKIP] [`97054a7`](https://git.odit.services/lfk/frontend/commit/97054a71c1ab8a045762a55148124965c6994373) - new license file version [CI SKIP] [`97054a7`](https://git.odit.services/lfk/frontend/commit/97054a71c1ab8a045762a55148124965c6994373)
@@ -1184,7 +1329,7 @@ All notable changes to this project will be documented in this file. Dates are d
- init [`32357ec`](https://git.odit.services/lfk/frontend/commit/32357ece0a7195ea1135c9c3e4c6c84323f95b4d) - init [`32357ec`](https://git.odit.services/lfk/frontend/commit/32357ece0a7195ea1135c9c3e4c6c84323f95b4d)
- tmp [`1b7173c`](https://git.odit.services/lfk/frontend/commit/1b7173cda9134ee8058a00bdc030defa80d46bfc) - tmp [`1b7173c`](https://git.odit.services/lfk/frontend/commit/1b7173cda9134ee8058a00bdc030defa80d46bfc)
- Login - move to env.js import [`8ef0b21`](https://git.odit.services/lfk/frontend/commit/8ef0b21819309752c573d0485f6514152fb684e6) - Login - move to env.js import [`8ef0b21`](https://git.odit.services/lfk/frontend/commit/8ef0b21819309752c573d0485f6514152fb684e6)
- initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae) - initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae)
- Initial license export [`4c96b9a`](https://git.odit.services/lfk/frontend/commit/4c96b9a3e04dbb7c021c71aa8828a29248509fbe) - Initial license export [`4c96b9a`](https://git.odit.services/lfk/frontend/commit/4c96b9a3e04dbb7c021c71aa8828a29248509fbe)
- 🚚 move to tinro svelte router [`a50ea15`](https://git.odit.services/lfk/frontend/commit/a50ea15b38023b867a9f7757e973184cbcdd2457) - 🚚 move to tinro svelte router [`a50ea15`](https://git.odit.services/lfk/frontend/commit/a50ea15b38023b867a9f7757e973184cbcdd2457)
- new Dashboard [`7270ce9`](https://git.odit.services/lfk/frontend/commit/7270ce9d32869abd4f6ac65ab7c2c87363633cbe) - new Dashboard [`7270ce9`](https://git.odit.services/lfk/frontend/commit/7270ce9d32869abd4f6ac65ab7c2c87363633cbe)

View File

@@ -1,12 +1,15 @@
FROM registry.odit.services/hub/library/node:15.14.0-alpine3.13 FROM registry.odit.services/hub/library/node:19.7.0-alpine3.16 as build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app WORKDIR /app
COPY package.json ./
RUN yarn COPY package.json pnpm-lock.yaml *.config.js *.config.cjs index.html ./
COPY package.json *.config.js postcss.config.cjs index.html ./ RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8 && pnpm i
COPY src ./src COPY src ./src
COPY public ./public COPY public ./public
RUN yarn build RUN pnpm build
# final image # final image
FROM registry.odit.services/hub/fholzer/nginx-brotli:v1.19.1 FROM registry.odit.services/library/nginx-brotli:3.15 as final
COPY --from=0 /app/dist /usr/share/nginx/html COPY --from=build /app/dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf COPY ./nginx.conf /etc/nginx/nginx.conf

View File

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

View File

@@ -1,56 +1,64 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "0.15.6", "version": "0.17.3",
"scripts": { "type": "module",
"i18n-order": "node order.js", "scripts": {
"dev": "vite", "i18n-order": "node order.js",
"build": "vite build", "dev": "vite",
"release": "release-it", "build": "vite build",
"licenses:export": "license-exporter --json -o public" "release": "release-it",
}, "licenses:export": "license-exporter --json -o public"
"license": "CC-BY-NC-SA-4.0", },
"devDependencies": { "license": "CC-BY-NC-SA-4.0",
"@odit/lfk-client-js": "0.11.0", "devDependencies": {
"@odit/license-exporter": "0.0.11", "@odit/license-exporter": "0.0.12",
"@sveltejs/vite-plugin-svelte": "1.0.0-next.6", "@sveltejs/vite-plugin-svelte": "2.0.4",
"@types/html-minifier": "4.0.0", "@types/html-minifier": "4.0.2",
"auto-changelog": "2.2.1", "auto-changelog": "2.4.0",
"autoprefixer": "^10.2.5", "autoprefixer": "10.4.14",
"check-password-strength": "2.0.2", "html-minifier": "4.0.0",
"csvtojson": "2.0.10", "postcss": "8.4.21",
"gridjs": "3.4.0", "release-it": "15.10.1",
"html-minifier": "4.0.0", "svelte-select": "3.17.0",
"localforage": "1.9.0", "tailwindcss": "3.3.1",
"marked": "2.0.3", "vite": "4.2.1"
"postcss": "^8.2.10", },
"release-it": "14.6.1", "release-it": {
"svelte": "3.37.0", "git": {
"svelte-focus-trap": "1.2.0", "commit": true,
"svelte-i18n": "3.3.9", "requireCleanWorkingDir": false,
"svelte-preprocess": "^4.7.0", "commitMessage": "🚀RELEASE v${version}",
"svelte-select": "3.17.0", "push": false,
"tailwindcss": "^2.1.1", "tag": true,
"tinro": "0.6.1", "tagName": null,
"toastify-js": "1.10.0", "tagAnnotation": "v${version}"
"validator": "13.5.2", },
"vite": "2.1.5", "npm": {
"xlsx": "0.16.9" "publish": false
}, },
"release-it": { "hooks": {
"git": { "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"
"commit": true, }
"requireCleanWorkingDir": false, },
"commitMessage": "🚀RELEASE v${version}", "dependencies": {
"push": false, "@odit/lfk-client-js": "0.14.3",
"tag": true, "@paralleldrive/cuid2": "^2.2.0",
"tagName": null, "@tanstack/svelte-table": "^8.8.5",
"tagAnnotation": "v${version}" "@tanstack/table-core": "^8.8.5",
}, "@vincjo/datatables": "^1.5.2",
"npm": { "check-password-strength": "2.0.7",
"publish": false "csvtojson": "2.0.10",
}, "gridjs": "3.4.0",
"hooks": { "localforage": "1.10.0",
"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" "marked": "2.0.3",
} "svelte": "3.58.0",
} "svelte-i18n": "3.6.0",
} "tinro": "0.6.12",
"toastify-js": "1.12.0",
"validator": "13.9.0",
"xlsx": "0.18.5"
},
"volta": {
"node": "19.7.0"
}
}

4047
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -72,6 +72,8 @@
import Scans from "./components/scans/Scans.svelte"; import Scans from "./components/scans/Scans.svelte";
import ScanDetail from "./components/scans/ScanDetail.svelte"; import ScanDetail from "./components/scans/ScanDetail.svelte";
import Cards from "./components/cards/Cards.svelte"; import Cards from "./components/cards/Cards.svelte";
import StatsClients from "./components/statsclients/StatsClients.svelte";
import StatsClientDetail from "./components/statsclients/StatsClientDetail.svelte";
store.init(); store.init();
</script> </script>
@@ -206,6 +208,14 @@
<ScanStationDetail {params} /> <ScanStationDetail {params} />
</Route> </Route>
</Route> </Route>
<Route path="/statsclients/*">
<Route path="/">
<StatsClients />
</Route>
<Route path="/:clientid" let:params>
<StatsClientDetail {params} />
</Route>
</Route>
<Route path="/about"> <Route path="/about">
<About /> <About />
</Route> </Route>

View File

@@ -1,7 +1,7 @@
<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 { 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;
@@ -133,7 +133,7 @@
{#if bulk_modal_open} {#if bulk_modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
bulk_modal_open = false; bulk_modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { import {
RunnerCardService, RunnerCardService,
RunnerService, RunnerService,
@@ -11,22 +11,37 @@
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) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; const getRunnerLabel = (option) => {
const filterRunners = (label, filterText, option) => if (option.middlename) {
label.toLowerCase().includes(filterText.toLowerCase()) || return option.firstname + " " + option.middlename + " " + option.lastname;
option.value.toString().startsWith(filterText.toLowerCase()); }
return option.firstname + " " + option.lastname;
};
const filterRunners = (label, filterText, option) => {
if (filterText.startsWith("#")) {
return option.value.id == parseInt(filterText.replace("#",""))
}
return (
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase())
);
};
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
$: runner = 0; $: runner = 0;
$: runners = [];
$: enabled = true; $: enabled = true;
$: processed_last_submit = true; $: processed_last_submit = true;
let loading = true;
let runners = [];
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 };
}); });
loading = false;
}); });
$: createbtnenabled = true; $: createbtnenabled = true;
(() => { (() => {
@@ -82,65 +97,82 @@
{#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: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="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> 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} bind:loading
placeholder={$_('search-for-runner-by-name-or-id')} showChevron={!loading}
noOptionsMessage={$_('no-runners-found')} placeholder={$_("search-for-runner-by-name-or-id")}
on:select={(selectedValue) => (runner = selectedValue.detail.value.id)} noOptionsMessage={$_("no-runners-found")}
on:clear={() => (runner = null)} /> on:select={(selectedValue) =>
(runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -152,16 +184,18 @@
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>

View File

@@ -1,10 +1,11 @@
<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 { 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";
import { createEventDispatcher } from "svelte";
export let edit_modal_open; export let edit_modal_open;
export let current_cards; export let current_cards;
export let runner = {}; export let runner = {};
@@ -12,15 +13,26 @@
export let original_data = {}; export let original_data = {};
const getRunnerLabel = (option) => const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) => const filterRunners = (label, filterText, option) => {
label.toLowerCase().includes(filterText.toLowerCase()) || if (filterText.startsWith("#")) {
option.value.toString().startsWith(filterText.toLowerCase()); return option.value.id == parseInt(filterText.replace("#",""))
}
return (
label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase())
);
};
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
$: runners = []; $: runners = [];
$: enabled = true; $: enabled = true;
$: processed_last_submit = true; $: processed_last_submit = true;
const dispatch = createEventDispatcher();
function dataUpdated() {
dispatch('dataUpdated',);
}
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 };
@@ -65,6 +77,7 @@
}).showToast(); }).showToast();
current_cards[current_cards.findIndex((c) => c.id === id)] = result; current_cards[current_cards.findIndex((c) => c.id === id)] = result;
current_cards = current_cards; current_cards = current_cards;
dataUpdated();
}) })
.catch((err) => { .catch((err) => {
// //
@@ -81,7 +94,7 @@
{#if edit_modal_open} {#if edit_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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
edit_modal_open = false; edit_modal_open = false;

View File

@@ -3,279 +3,270 @@
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 { DataHandler, Datatable, Th, ThFilter } from "@vincjo/datatables";
import CardsEmptyState from "./CardsEmptyState.svelte"; import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte"; import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import ThFilterStatus from "./ThFilterStatus.svelte";
import ThFilterRunner from "./ThFilterRunner.svelte";
export let edit_modal_open = false; export let edit_modal_open = false;
export let runner = {}; export let runner = {};
export let editable = {}; export let editable = {};
export let original_data = {}; export let original_data = {};
export let current_cards = []; export let current_cards = [];
$: filtered_cards = current_cards.filter(function (c) { const handler = new DataHandler(current_cards, { rowsPerPage: 50 });
if ( const rows = handler.getRows();
c.code.toLowerCase().includes(searchvalue_lowercase) ||
c.runner?.firstname.toLowerCase().includes(searchvalue_lowercase) ||
c.runner?.middlename.toLowerCase().includes(searchvalue_lowercase) ||
c.runner?.lastname.toLowerCase().includes(searchvalue_lowercase) ||
should_display_based_on_id(c.id)
) {
return true;
}
});
$: searchvalue = "";
$: searchvalue_lowercase = searchvalue.toLowerCase();
$: active_deletes = []; $: active_deletes = [];
$: cards_show = current_cards.some((r) => r.is_selected === true); $: cards_show = generate_cards.length > 0;
$: generate_cards = current_cards.filter((r) => r.is_selected === true); $: generate_cards = [];
const cards_promise = RunnerCardService.runnerCardControllerGetAll().then( const cards_promise = RunnerCardService.runnerCardControllerGetAll().then(
(val) => { (val) => {
current_cards = val; current_cards = val;
handler.setRows(val);
} }
); );
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
const getRunnerLabel = (option) => const getRunnerLabel = (option) =>
option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}"); option?.firstname +
" " +
(option?.middlename || "") +
" " +
(option?.lastname || "{$_('non-blanko')}");
function open_edit_modal(card) { function open_edit_modal(card) {
if(card.runner?.id){ if (card.runner?.id) {
runner = Object.assign( runner = Object.assign(
{ runner }, { runner },
{ label: getRunnerLabel(card.runner), value: card.runner } { label: getRunnerLabel(card.runner), value: card.runner }
); );
card.runner = card.runner.id; card.runner = card.runner.id;
} } else {
else{ card.runner = null;
card.runner=null; runner = null;
runner = null
} }
editable = Object.assign(editable, card); editable = Object.assign(editable, card);
original_data = Object.assign(original_data, card); original_data = Object.assign(original_data, card);
edit_modal_open = true; edit_modal_open = true;
} }
// -----------------
let scrollTop = 0;
$: rendered = filtered_cards;
let innerHeight = 0;
let ele;
$: updateSlice(scrollTop);
$: innerHeight = `${filtered_cards.length * 25}px`;
$: if (ele) updateSlice();
function updateSlice() {
const height = ele ? parseInt(ele.clientHeight) : 100;
const init = scrollTop / 25;
const end = Math.ceil((scrollTop + height) / 25);
rendered = filtered_cards.slice(init, end + 15);
}
function updateScroll($event) {
scrollTop = $event.target.scrollTop;
}
</script> </script>
<style> {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:UPDATE")}
table tbody {
display: block;
overflow-y: scroll;
}
table thead, table tbody tr {
display: table;
width: 100%;
table-layout: fixed;
}
</style>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:UPDATE')}
<CardDetailModal <CardDetailModal
bind:current_cards bind:current_cards
bind:edit_modal_open bind:edit_modal_open
bind:runner bind:runner
bind:editable bind:editable
bind:original_data /> bind:original_data
on:dataUpdated={handler.setRows(current_cards)}
/>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")}
{#await cards_promise} {#await cards_promise}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"> role="alert"
<p class="font-bold">{$_('loading-cards')}</p> >
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <p class="font-bold">{$_("loading-cards")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:then} {:then}
{#if current_cards.length === 0} {#if current_cards.length === 0}
<CardsEmptyState /> <CardsEmptyState />
{:else} {:else}
<input <div class="h-12 mt-1">
type="search" {#if cards_show}
bind:value={searchvalue} <button
placeholder={$_('datatable.search')} type="button"
aria-label={$_('datatable.search')} 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 inline-flex"
class="gridjs-input gridjs-search-input mb-4" /> id="options-menu"
<div class="h-12"> on:click={async () => {
<GenerateRunnerCards const prom = [];
bind:cards_show for (const card of generate_cards) {
bind:generate_cards /> prom.push(
</div> RunnerCardService.runnerCardControllerRemove(card.id, true)
<div );
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> }
<table class="divide-y divide-gray-200 w-full"> await Promise.all(prom);
<thead class="bg-gray-50"> Toastify({
text: $_("cards-deleted"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
//TODO: Delete cards from table
}}
>
{$_("delete-cards")}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
{/if}
<GenerateRunnerCards bind:cards_show bind:generate_cards />
</div>
<Datatable {handler}>
<table>
<thead>
<tr> <tr>
<th <th style="border-bottom: 1px solid #ddd;">
scope="col" <input
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> type="checkbox"
<span class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
checked={generate_cards.length == current_cards.length}
on:click={() => { on:click={() => {
const newstate = !current_cards.some((r) => r.is_selected === true); if (generate_cards.length != current_cards.length) {
current_cards = current_cards.map((r) => { generate_cards = current_cards;
r.is_selected = newstate; } else {
return r; generate_cards = [];
}); }
}} }}
class="underline cursor-pointer select-none">{#if current_cards.some((r) => r.is_selected === true)} />
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('code')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('status')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th> </th>
<Th {handler} orderBy="code">{$_("code")}</Th>
<Th {handler} orderBy="runner">{$_("runner")}</Th>
<Th {handler} orderBy="status">{$_("status")}</Th>
<th style="border-bottom: 1px solid #ddd;">{$_("action")}</th>
</tr>
<tr>
<th style="border-bottom: 1px solid #ddd;" />
<ThFilter {handler} filterBy="code" />
<ThFilterRunner {handler} />
<ThFilterStatus {handler} />
<th style="border-bottom: 1px solid #ddd;" />
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-gray-200 virtual-wrapper" <tbody>
on:scroll={updateScroll} {#each $rows as row}
style="height: 70vh; width:100%" <tr>
bind:this={ele} <td>
> <input
{#each filtered_cards as card, index} type="checkbox"
{#if card.code class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
.toLowerCase() checked={generate_cards.filter((i) => i.id == row.id)
.includes( .length > 0}
searchvalue.toLowerCase() on:click={() => {
) || card.runner?.firstname if (
.toLowerCase() generate_cards.findIndex((i) => i.id == row.id) == -1
.includes( ) {
searchvalue.toLowerCase() generate_cards.push(row);
) || card.runner?.middlename generate_cards = generate_cards;
.toLowerCase() } else {
.includes( generate_cards = generate_cards.filter(
searchvalue.toLowerCase() (r) => r.id != row.id
) || card.runner?.lastname );
.toLowerCase() }
.includes( console.log(generate_cards);
searchvalue.toLowerCase() }}
) || should_display_based_on_id(card.id)} />
<tr data-rowid="card_{card.id}"> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td>{row.code}</td>
<input <td>
bind:checked={card.is_selected} {#if row.runner}
type="checkbox" <a
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> href="../runners/{row.runner.id}"
</td> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
<td class="px-6 py-4 whitespace-nowrap"> >{row.runner.firstname}
<div class="flex items-center">{card.code}</div> {row.runner.middlename || ""}
</td> {row.runner.lastname}</a
<td class="px-6 py-4 whitespace-nowrap"> >
<div class="flex items-center"> {:else}{$_("non-blanko")}{/if}
{#if card.runner} </td>
<a <td>
href="../runners/{card.runner.id}" {#if row.enabled}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname} <span
{card.runner.middlename || ''} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
{card.runner.lastname}</a> >{$_("enabled")}</span
{:else}{$_('non-blanko')}{/if} >
</div> {:else}
</td> <span
<td class="px-6 py-4 whitespace-nowrap"> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
<div class="flex items-center"> >{$_("disabled")}</span
{#if card.enabled} >
<span {/if}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled')}</span> </td>
{:else} <td>
<span {#if active_deletes[row.id] === true}
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span> <button
{/if} on:click={() => {
</div> active_deletes[row.id] = false;
</td> }}
tabindex="0"
{#if active_deletes[card.id] === true} class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
<td >{$_("cancel-delete")}</button
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> >
<button <button
on:click={() => { on:click={() => {
active_deletes[card.id] = false; RunnerCardService.runnerCardControllerRemove(
}} row.id,
tabindex="0" true
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> )
<button .then((resp) => {
on:click={() => { current_cards = current_cards.filter(
RunnerCardService.runnerCardControllerRemove(card.id, false).then( (obj) => obj.id !== row.id
(resp) => { );
current_cards = current_cards.filter( })
(obj) => obj.id !== card.id .catch((err) => {});
); }}
Toastify({ tabindex="0"
text: $_('card-deleted'), class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
duration: 500, >{$_("confirm-delete")}</button
backgroundColor: >
'linear-gradient(to right, #00b09b, #96c93d)', {:else}
}).showToast(); <button
} on:click={() => {
); open_edit_modal(row);
}} }}
tabindex="0" class="text-indigo-600 hover:text-indigo-900"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> >{$_("details")}</button
</td> >
{:else} {#if store.state.jwtinfo.userdetails.permissions.includes("CARD:DELETE")}
<td <button
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> on:click={() => {
<button active_deletes[row.id] = true;
on:click={() => { }}
open_edit_modal(card); tabindex="0"
}} class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button> >{$_("delete")}</button
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')} >
<button {/if}
on:click={() => { {/if}
active_deletes[card.id] = true; </td>
}} </tr>
tabindex="0" {/each}
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody> </tbody>
</table> </table>
</div> </Datatable>
{/if} {/if}
{:catch error} {:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <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"> <span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b> <b class="capitalize">{$_("general_promise_error")}</b>
{error} {error}
</span> </span>
</div> </div>
{/await} {/await}
{/if} {/if}
<style>
table tbody {
display: block;
overflow-y: scroll;
}
table thead,
table tbody tr {
display: table;
width: 100%;
table-layout: fixed;
}
</style>

View File

@@ -0,0 +1,57 @@
<script>
export let handler;
let filterValue = "";
</script>
<th>
<input
on:input={() => {
setTimeout(() => {
const v = filterValue.toLowerCase();
handler.filter(v, (c) => {
// if (v === "") {
// return c;
// }
if (!c.runner && v === "blanko") {
return "blanko";
}
if (v.startsWith("#")) {
return `#${c.runner?.id}`;
}
if (c.runner) {
let runnerName = `${c.runner.firstname} ${c.runner.lastname}`;
if (c.runner.middlename) {
runnerName = `${c.runner.firstname} ${c.runner.middlename} ${c.runner.lastname}`;
}
runnerName = runnerName.toLowerCase();
return runnerName;
}
return "";
});
}, 150);
}}
bind:value={filterValue}
type="text"
name="runnerfilter"
id="runnerfilter"
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,45 @@
<script>
import { _ } from "svelte-i18n";
export let handler;
let selected = "all";
</script>
<th>
<select
on:input={() => {
setTimeout(() => {
if (`${selected}`.trim()) {
if (selected === "all") {
handler.filter("", "enabled");
} else {
handler.filter(selected, "enabled");
}
}
}, 50);
}}
bind:value={selected}
name="statusfilter"
id="statusfilter"
>
<option value="all">{$_("all")}</option>
<option value="true">{$_("enabled")}</option>
<option value="false">{$_("disabled")}</option>
</select>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
select {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -1,7 +1,7 @@
<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 { import {
GroupContactService, GroupContactService,
RunnerTeamService, RunnerTeamService,
@@ -145,7 +145,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -256,6 +256,26 @@
<span>{$_('scanstations')}</span> <span>{$_('scanstations')}</span>
</a> </a>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:GET')}
<a
class:bg-gray-100={$router.path === '/statsclients/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/statsclients/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><path
fill="none"
d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
<span>{$_('statsclients')}</span>
</a>
{/if}
<a <a
class:bg-gray-100={$router.path === '/settings/'} class:bg-gray-100={$router.path === '/settings/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900" class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"

View File

@@ -1,22 +1,157 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import StatCards from "./StatCards.svelte"; import { StatsService } from "@odit/lfk-client-js";
import StatCards from "./StatCard.svelte";
import store from "../../store"; import store from "../../store";
import StatCard from "./StatCard.svelte";
let navOpen = false; let navOpen = false;
const stats_promise = StatsService.statsControllerGet();
</script> </script>
<div <div
class="p-5 overflow-x-hidden" class="p-5 overflow-x-hidden"
on:click={() => { on:click={() => {
navOpen = false; navOpen = false;
}}> }}
>
<h1 class="text-3xl leading-tight"> <h1 class="text-3xl leading-tight">
<span class="font-extrabold">{$_('dashboard-title')}</span> <span class="font-extrabold">{$_("dashboard-title")}</span>
<span> <span>
- -
{$_('dashboard-greeting')}, {$_("dashboard-greeting")},
<span <span class="text-blue-500"
class="text-blue-500">{store.state.jwtinfo.userdetails.firstname} {store.state.jwtinfo.userdetails.lastname}</span></span> >{store.state.jwtinfo.userdetails.firstname}
{store.state.jwtinfo.userdetails.lastname}</span
></span
>
</h1> </h1>
<StatCards /> <h1>{$_("general-stats")}</h1>
{#await stats_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("stats-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then stats}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
<StatCard
title={$_("runners")}
value={stats.total_runners}
href="/runners/"
>
<svg
height="24"
width="24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path d="M0 0h24v24H0z" fill="none" />
<path
d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-scans")}
value={stats.total_scans}
href="/scans/"
>
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"
><polyline points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg
>
</StatCard>
<StatCard
title={$_("total-donations")}
value={`${(stats.total_donation / 100).toFixed(2)} €`}
href="/donations/"
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
fill="currentColor"
width="24"
><path d="M0 0h24v24H0z" fill="none" />
<path
d="M15 18.5A6.48 6.48 0 019.24 15H15v-2H8.58c-.05-.33-.08-.66-.08-1s.03-.67.08-1H15V9H9.24A6.491 6.491 0 0115 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3A8.955 8.955 0 0015 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06a8.262 8.262 0 000 2H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77c-1.13.98-2.6 1.57-4.22 1.57z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-distance")}
value={`${stats.total_distance / 1000} km`}
href="#"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
height="24"
width="24"
><path d="M0 0h24v24H0z" fill="none" />
<path
d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"
/></svg
>
</StatCard>
<StatCard
title={$_("count_teams")}
value={stats.total_teams}
href="/teams/"
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"
><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg
>
</StatCard>
<StatCard
title={$_("count_organizations")}
value={stats.total_orgs}
href="/orgs/"
>
<svg
height="24"
fill="currentColor"
width="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 11V3H7v4H3v14h8v-4h2v4h8V11h-4zM7 19H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm4 4H9v-2h2v2zm0-4H9V9h2v2zm0-4H9V5h2v2zm4 8h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm4 12h-2v-2h2v2zm0-4h-2v-2h2v2z"
/></svg
>
</StatCard>
</div>
{: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}
</div> </div>

View File

@@ -0,0 +1,22 @@
<script>
import { _ } from "svelte-i18n";
export let href = "#"
export let title = "";
export let value = "";
</script>
<a href={href}>
<div
class="p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{title}
</div>
<div class="text-xl font-bold">{value}</div>
</div>
<slot></slot>
</div>
</div>
</a>

View File

@@ -1,165 +0,0 @@
<script>
import { StatsService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
const stats_promise = StatsService.statsControllerGet();
</script>
<!-- -->
<h1>{$_('general-stats')}</h1>
{#await stats_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('stats-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then stats}
<div
class="flex flex-col lg:flex-row w-full lg:space-x-2 space-y-2 lg:space-y-0 mb-2 lg:mb-4">
<a href="/runners/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('runners')}
</div>
<div class="text-xl font-bold">{stats.total_runners}</div>
</div>
<svg
height="24"
width="24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z" /></svg>
</div>
</div>
</a>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-scans')}
</div>
<div class="text-xl font-bold">{stats.total_scans}</div>
</div><svg
stroke="currentColor"
fill="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><polyline
points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-donations')}
</div>
<div class="text-xl font-bold">{(stats.total_donation/100).toFixed(2)}</div>
</div><svg
xmlns="http://www.w3.org/2000/svg"
height="24"
fill="currentColor"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M15 18.5A6.48 6.48 0 019.24 15H15v-2H8.58c-.05-.33-.08-.66-.08-1s.03-.67.08-1H15V9H9.24A6.491 6.491 0 0115 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3A8.955 8.955 0 0015 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06a8.262 8.262 0 000 2H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77c-1.13.98-2.6 1.57-4.22 1.57z" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-distance')}
</div>
<div class="text-xl font-bold">
{stats.total_distance / 1000}
km
</div>
</div>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
height="24"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z" /></svg>
</div>
</div>
</div>
<a href="/teams/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('count_teams')}
</div>
<div class="text-xl font-bold">{stats.total_teams}</div>
</div>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>
</div>
</div>
</a>
<a href="/orgs/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('count_organizations')}
</div>
<div class="text-xl font-bold">{stats.total_orgs}</div>
</div>
<svg
height="24"
fill="currentColor"
width="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 11V3H7v4H3v14h8v-4h2v4h8V11h-4zM7 19H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm4 4H9v-2h2v2zm0-4H9V9h2v2zm0-4H9V5h2v2zm4 8h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm4 12h-2v-2h2v2zm0-4h-2v-2h2v2z" /></svg>
</div>
</div>
</a>
</div>
{: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}

View File

@@ -1,7 +1,7 @@
<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 { import {
DonationService, DonationService,
DonorService, DonorService,
@@ -156,7 +156,7 @@ import { is_promise } from "svelte/internal";
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { DonationService } from "@odit/lfk-client-js"; import { DonationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let payment_modal_open = false; export let payment_modal_open = false;
@@ -96,7 +96,7 @@
{#if payment_modal_open} {#if payment_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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
payment_modal_open = false; payment_modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { import {
DonorService DonorService
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
@@ -134,7 +134,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { DonorService } from "@odit/lfk-client-js"; import { DonorService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@@ -32,7 +32,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={cancelDelete}> on:click_outside={cancelDelete}>
<div <div

View File

@@ -207,7 +207,7 @@
<a <a
href="../donations/{d.id}" href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1">{d.runner.firstname}
{d.runner.middlename} {d.runner.middlename || ""}
{d.runner.lastname}</a> {d.runner.lastname}</a>
{:else} {:else}
<a <a

View File

@@ -1,7 +1,7 @@
<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";
export let modal_open; export let modal_open;
(function () { (function () {
document.onkeydown = function (e) { document.onkeydown = function (e) {
@@ -25,7 +25,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -2,7 +2,7 @@
import { _, getLocaleFromNavigator } from "svelte-i18n"; import { _, getLocaleFromNavigator } from "svelte-i18n";
import marked from "marked"; import marked from "marked";
import Footer from "./Footer.svelte"; import Footer from "./Footer.svelte";
import * as css from "../base/simple.css"; // import * as css from "../base/simple.css?inline";
let html = ""; let html = "";
async function load() { async function load() {
let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md"); let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md");

View File

@@ -1,7 +1,7 @@
<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 Toastify from "toastify-js"; import Toastify from "toastify-js";
import { UserGroupService } from "@odit/lfk-client-js"; import { UserGroupService } from "@odit/lfk-client-js";
export let modal_open; export let modal_open;
@@ -69,7 +69,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { RunnerOrganizationService } from "@odit/lfk-client-js"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let modal_open; export let modal_open;
@@ -89,7 +89,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { RunnerOrganizationService } from "@odit/lfk-client-js"; import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@@ -32,7 +32,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={cancelDelete}> on:click_outside={cancelDelete}>
<div <div

View File

@@ -326,14 +326,14 @@
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input <input
bind:checked={editable.registrationEnabled} bind:checked={editable.registrationEnabled}
id="comments" id="toggle_selfservice_feature"
name="comments" name="toggle_selfservice_feature"
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>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label <label
for="comments" for="toggle_selfservice_feature"
class="font-medium text-gray-700">{$_('selfservice-registration')}</label> class="font-medium text-gray-700">{$_('selfservice-registration')}</label>
</div> </div>
</div> </div>
@@ -375,14 +375,14 @@
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input <input
bind:checked={editable.address_checked} bind:checked={editable.address_checked}
id="comments" id="toggle_address_checkbox"
name="comments" name="toggle_address_checkbox"
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>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label <label
for="comments" for="toggle_address_checkbox"
class="font-medium text-gray-700">{$_('address')}</label> class="font-medium text-gray-700">{$_('address')}</label>
</div> </div>
</div> </div>

View File

@@ -1,403 +1,418 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { import {
RunnerCardService, RunnerCardService,
RunnerOrganizationService, RunnerOrganizationService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let cards_show = false; import { init } from "@paralleldrive/cuid2";
export let generate_cards = []; const createId = init({ length: 10, fingerprint: "lfk-frontend" });
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) { export let cards_show = false;
cards_dropdown_open = 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;
}
});
if (generate_orgs.length > 0) { function generateRunnerCards(locale) {
generateOrgCards(locale); cards_dropdown_open = false;
} else if (generate_teams.length > 0) {
generateTeamCards(locale); if (generate_orgs.length > 0) {
} else if (generate_runners.length > 0) { generateOrgCards(locale);
generateRunnersCards(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_documentserver}/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 { } else {
generateCards(locale); return response.blob();
} }
} })
.then((blob) => {
function generateCards(locale) { const url = window.URL.createObjectURL(blob);
const toast = Toastify({ let a = document.createElement("a");
text: $_("generating-pdf"), a.href = url;
duration: -1, a.download = `${$_("runnercards")}-${locale}-${createId()}.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(); }).showToast();
fetch( })
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`, .catch((err) => {
{ console.error(err);
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) { async function generateRunnersCards(locale) {
const toast = Toastify({ const toast = Toastify({
text: $_("generating-pdf"), text: $_("generating-pdf"),
duration: -1, 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_documentserver}/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}-${createId()}.pdf`;
} else {
a.download = `${$_("runnercards")}-${locale}-${createId()}.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(); }).showToast();
const current_cards = await RunnerCardService.runnerCardControllerGetAll(); })
.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_documentserver}/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}-${createId()}.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-pdfs"),
duration: -1,
}).showToast();
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let count = 0;
let count_orgs = 0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
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);
}
await fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("runnercards")}_${o.name}_direct-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
console.log("here");
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let cards = []; let cards = [];
for (let runner of generate_runners) { for (let runner of runners) {
let card = current_cards.find((c) => c.runner?.id == runner.id); let card = current_cards.find((c) => c.runner?.id == runner.id);
if (!card) { if (!card) {
card = await RunnerCardService.runnerCardControllerPost({ card = await RunnerCardService.runnerCardControllerPost({
runner: runner.id, runner: runner.id,
}); });
} }
cards.push(card); cards.push(card);
} }
fetch( await fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`, `${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(cards), body: JSON.stringify(cards),
} }
) )
.then((response) => { .then((response) => {
if (response.status != "200") { if (response.status != "200") {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-generation-failed"), text: $_("pdf-generation-failed"),
duration: 3500, duration: 3500,
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%)",
}).showToast(); }).showToast();
} else { } else {
return response.blob(); return response.blob();
} }
}) })
.then((blob) => { .then((blob) => {
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
let a = document.createElement("a"); let a = document.createElement("a");
a.href = url; a.href = url;
if(generate_runners.length == 1){ a.download = `${$_("runnercards")}_${o.name}_${
a.download = `${$_('runnercards')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`; t.name
} }-${locale}-${createId()}.pdf`;
else{ document.body.appendChild(a);
a.download = `Runnercards-${locale}.pdf`; a.click();
} a.remove();
document.body.appendChild(a); if (
a.click(); count === o.teams.length &&
a.remove(); count_orgs === generate_orgs.length
toast.hideToast(); ) {
Toastify({ toast.hideToast();
text: $_("pdf-successfully-generated"), Toastify({
duration: 3500, text: $_("pdfs-successfully-generated"),
backgroundColor: duration: 3500,
"linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) }
.catch((err) => {}); })
} .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_documentserver}/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-pdfs"),
duration: -1,
}).showToast();
const current_cards = await RunnerCardService.runnerCardControllerGetAll();
let count = 0;
let count_orgs =0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(o.id, true)
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);
}
await fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}_${o.name}_direct-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
console.log("here")
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let 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);
}
await fetch(
`${config.baseurl_documentserver}/cards?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cards),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('runnercards')}_${o.name}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
} }
}
</script> </script>
{#if cards_show} {#if cards_show}
<div id="cards:dropdown" class="relative inline-block"> <div id="cards:dropdown" class="relative inline-block">
<div> <div>
<button <button
on:click={() => { on:click={() => {
cards_dropdown_open = !cards_dropdown_open; cards_dropdown_open = !cards_dropdown_open;
}} }}
type="button" 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" 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" id="options-menu"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="true"> aria-expanded="true"
{$_('generate-runnercards')} >
<svg {$_("generate-runnercards")}
xmlns="http://www.w3.org/2000/svg" <svg
width="24" xmlns="http://www.w3.org/2000/svg"
height="24" width="24"
viewBox="0 0 24 24" height="24"
class="-mr-1 ml-2 h-5 w-5"><path viewBox="0 0 24 24"
fill="none" class="-mr-1 ml-2 h-5 w-5"
d="M0 0h24v24H0z" /> ><path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" 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> d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
</button> /></svg
</div> >
{#if cards_dropdown_open} </button>
<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> </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 z-10"
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} {/if}

View File

@@ -1,332 +1,355 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { import {
DonationService, DonationService,
RunnerTeamService, RunnerTeamService,
RunnerOrganizationService RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let certificates_show = false; import { init } from "@paralleldrive/cuid2";
export let generate_runners = []; const createId = init({ length: 10, fingerprint: "lfk-frontend" });
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) { export let certificates_show = false;
certificates_dropdown_open = 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;
}
});
if (generate_orgs.length > 0) { function generateCertificates(locale) {
generateOrgCertificates(locale); certificates_dropdown_open = false;
} else if (generate_teams.length > 0) {
generateTeamCertificates(locale); 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.filter((d) => d.runner?.id == runner.id) || [];
console.log(runner.distanceDonations);
certificateRunners.push(runner);
}
fetch(
`${config.baseurl_documentserver}/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 { } else {
generateRunnerCertificates(locale); return response.blob();
} }
} })
.then((blob) => {
async function generateRunnerCertificates(locale) { const url = window.URL.createObjectURL(blob);
const toast = Toastify({ let a = document.createElement("a");
text: $_("generating-pdf"), a.href = url;
duration: -1, if (generate_runners.length == 1) {
a.download = `${$_("certificates")}_${
generate_runners[0].firstname
}_${generate_runners[0].lastname}-${locale}-${createId()}.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(); }).showToast();
const current_donations = (await DonationService.donationControllerGetAll()) || []; })
.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.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl_documentserver}/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}-${createId()}.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-pdfs"),
duration: -1,
}).showToast();
const current_donations =
(await DonationService.donationControllerGetAll()) || [];
let count = 0;
let count_orgs = 0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners =
await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
o.id,
true
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations =
current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_("certificates")}_${o.name}-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
console.log("here");
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = []; let certificateRunners = [];
for (let runner of generate_runners) { for (let runner of runners) {
runner.distanceDonations = current_donations.filter((d) => d.runner?.id == runner.id) || []; runner.distanceDonations =
console.log(runner.distanceDonations) current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner); certificateRunners.push(runner);
} }
fetch( await fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`, `${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(certificateRunners), body: JSON.stringify(certificateRunners),
} }
) )
.then((response) => { .then((response) => {
if (response.status != "200") { if (response.status != "200") {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-generation-failed"), text: $_("pdf-generation-failed"),
duration: 3500, duration: 3500,
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%)",
}).showToast(); }).showToast();
} else { } else {
return response.blob(); return response.blob();
} }
}) })
.then((blob) => { .then((blob) => {
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
let a = document.createElement("a"); let a = document.createElement("a");
a.href = url; a.href = url;
if(generate_runners.length == 1){ a.download = `${$_("certificates")}_${o.name}_${
a.download = `${$_('certificates')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`; t.name
} }-${locale}-${createId()}.pdf`;
else{ document.body.appendChild(a);
a.download = `${$_('certificates')}-${locale}.pdf`; a.click();
} a.remove();
document.body.appendChild(a); if (
a.click(); count === o.teams.length &&
a.remove(); count_orgs === generate_orgs.length
toast.hideToast(); ) {
Toastify({ toast.hideToast();
text: $_("pdf-successfully-generated"), Toastify({
duration: 3500, text: $_("pdfs-successfully-generated"),
backgroundColor: duration: 3500,
"linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
}) }
.catch((err) => {}); })
} .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.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
fetch(
`${config.baseurl_documentserver}/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-pdfs"),
duration: -1,
}).showToast();
const current_donations = (await DonationService.donationControllerGetAll()) || [];
let count = 0;
let count_orgs =0;
for (const o of generate_orgs) {
count_orgs++;
let count = 0;
let runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(o.id, true)
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('certificates')}_${o.name}_direct-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
console.log("here")
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
let certificateRunners = [];
for (let runner of runners) {
runner.distanceDonations = current_donations.filter((d) => d.runner?.id == runner.id) || [];
certificateRunners.push(runner);
}
await fetch(
`${config.baseurl_documentserver}/certificates?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certificateRunners),
}
)
.then((response) => {
if (response.status != "200") {
toast.hideToast();
Toastify({
text: $_("pdf-generation-failed"),
duration: 3500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
} else {
return response.blob();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = `${$_('certificates')}_${o.name}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
}
} }
}
</script> </script>
{#if certificates_show} {#if certificates_show}
<div id="certificates:dropdown" class="relative inline-block"> <div id="certificates:dropdown" class="relative inline-block">
<div> <div>
<button <button
on:click={() => { on:click={() => {
certificates_dropdown_open = !certificates_dropdown_open; certificates_dropdown_open = !certificates_dropdown_open;
}} }}
type="button" 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" 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" id="options-menu"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="true"> aria-expanded="true"
{$_('generate-runner-certificates')} >
<svg {$_("generate-runner-certificates")}
xmlns="http://www.w3.org/2000/svg" <svg
width="24" xmlns="http://www.w3.org/2000/svg"
height="24" width="24"
viewBox="0 0 24 24" height="24"
class="-mr-1 ml-2 h-5 w-5"><path viewBox="0 0 24 24"
fill="none" class="-mr-1 ml-2 h-5 w-5"
d="M0 0h24v24H0z" /> ><path fill="none" d="M0 0h24v24H0z" />
<path <path
fill="currentColor" 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> d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
</button> /></svg
</div> >
{#if certificates_dropdown_open} </button>
<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> </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 z-10"
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} {/if}

View File

@@ -1,306 +1,324 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { import {
RunnerOrganizationService, RunnerOrganizationService,
RunnerTeamService, RunnerTeamService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let sponsoring_contracts_show = false; import { init } from "@paralleldrive/cuid2";
export let generate_runners = []; const createId = init({ length: 10, fingerprint: "lfk-frontend" });
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) { export let sponsoring_contracts_show = false;
sponsoring_contracts_download_open = false; export let generate_runners = [];
export let generate_orgs = [];
if (generate_orgs.length > 0) { export let generate_teams = [];
generateOrgContracts(locale); $: sponsoring_contracts_download_open = false;
} else if (generate_teams.length > 0) { document.addEventListener("click", function (e) {
generateTeamContracts(locale); if (
} else { e.target.parentNode?.parentNode?.id != "sponsoring:dropdown" &&
generateRunnerContracts(locale); e.target.parentNode?.parentNode?.id != "sponsoring:dropdown:menu"
} ) {
sponsoring_contracts_download_open = false;
} }
});
async function generateTeamContracts(locale) { function generateSponsoringContract(locale) {
const toast = Toastify({ sponsoring_contracts_download_open = false;
text: $_("generating-pdfs"),
duration: -1, if (generate_orgs.length > 0) {
}).showToast(); generateOrgContracts(locale);
let count = 0; } else if (generate_teams.length > 0) {
for (const t of generate_teams) { generateTeamContracts(locale);
count++; } else {
const runners = await RunnerTeamService.runnerTeamControllerGetRunners( generateRunnerContracts(locale);
t.id
);
fetch(
`${config.baseurl_documentserver}/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) { async function generateTeamContracts(locale) {
const toast = Toastify({ const toast = Toastify({
text: $_("generating-pdf"), text: $_("generating-pdfs"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
let count_orgs =0; let count = 0;
for (const o of generate_orgs) { for (const t of generate_teams) {
count_orgs++; count++;
let count = 0; const runners = await RunnerTeamService.runnerTeamControllerGetRunners(
let runners = await RunnerOrganizationService.runnerOrganizationControllerGetRunners(o.id, true) t.id
await fetch( );
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, fetch(
{ `${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
method: "POST", {
headers: { method: "POST",
"Content-Type": "application/json", headers: {
}, "Content-Type": "application/json",
body: JSON.stringify(runners), },
} 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')}_${o.name}_direct-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
console.log("here")
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
await fetch(
`${config.baseurl_documentserver}/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')}_${o.name}_${t.name}-${locale}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
}
} }
)
.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}-${createId()}.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) => {});
} }
}
function generateRunnerContracts(locale) { async function generateOrgContracts(locale) {
const toast = Toastify({ const toast = Toastify({
text: $_("generating-pdf"), text: $_("generating-pdf"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
fetch( let count_orgs = 0;
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`, for (const o of generate_orgs) {
{ count_orgs++;
method: "POST", let count = 0;
headers: { let runners =
"Content-Type": "application/json", await RunnerOrganizationService.runnerOrganizationControllerGetRunners(
}, o.id,
body: JSON.stringify(generate_runners), true
} );
await fetch(
`${config.baseurl_documentserver}/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")}_${o.name}_direct-${locale}-${createId()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
if (count === o.teams.length && count_orgs === generate_orgs.length) {
toast.hideToast();
console.log("here");
Toastify({
text: $_("pdfs-successfully-generated"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
})
.catch((err) => {});
for (const t of o.teams) {
count++;
let runners = await RunnerTeamService.runnerTeamControllerGetRunners(
t.id
);
await fetch(
`${config.baseurl_documentserver}/contracts?locale=${locale}&download=true&key=${config.documentserver_key}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(runners),
}
) )
.then((response) => { .then((response) => {
if (response.status != "200") { if (response.status != "200") {
toast.hideToast(); toast.hideToast();
Toastify({ Toastify({
text: $_("pdf-generation-failed"), text: $_("pdf-generation-failed"),
duration: 3500, duration: 3500,
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%)",
}).showToast(); }).showToast();
} else { } else {
return response.blob(); return response.blob();
} }
}) })
.then((blob) => { .then((blob) => {
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
let a = document.createElement("a"); let a = document.createElement("a");
a.href = url; a.href = url;
if(generate_runners.length == 1){ a.download = `${$_("sponsorings")}_${o.name}_${
a.download = `${$_('sponsorings')}_${generate_runners[0].firstname}_${generate_runners[0].lastname}-${locale}.pdf`; t.name
} }-${locale}-${createId()}.pdf`;
a.download = `${$_('sponsorings')}-${locale}.pdf`; document.body.appendChild(a);
document.body.appendChild(a); a.click();
a.click(); a.remove();
a.remove(); if (
toast.hideToast(); count === o.teams.length &&
Toastify({ count_orgs === generate_orgs.length
text: $_("pdf-successfully-generated"), ) {
duration: 3500, toast.hideToast();
backgroundColor: Toastify({
"linear-gradient(to right, #00b09b, #96c93d)", text: $_("pdfs-successfully-generated"),
}).showToast(); duration: 3500,
}) backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
.catch((err) => { }).showToast();
console.error(err); }
}); })
.catch((err) => {});
}
} }
}
function generateRunnerContracts(locale) {
const toast = Toastify({
text: $_("generating-pdf"),
duration: -1,
}).showToast();
fetch(
`${config.baseurl_documentserver}/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}-${createId()}.pdf`;
}
a.download = `${$_("sponsorings")}-${locale}-${createId()}.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> </script>
{#if sponsoring_contracts_show} {#if sponsoring_contracts_show}
<div id="sponsoring:dropdown" class="relative inline-block"> <div id="sponsoring:dropdown" class="relative inline-block">
<div> <div>
<button <button
on:click={() => { on:click={() => {
sponsoring_contracts_download_open = !sponsoring_contracts_download_open; 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" type="button"
id="options-menu" class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-600 text-base font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm inline-flex"
aria-haspopup="true" id="options-menu"
aria-expanded="true"> aria-haspopup="true"
{$_('generate-sponsoring-contracts')} aria-expanded="true"
<svg >
xmlns="http://www.w3.org/2000/svg" {$_("generate-sponsoring-contracts")}
width="24" <svg
height="24" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24"
class="-mr-1 ml-2 h-5 w-5"><path height="24"
fill="none" viewBox="0 0 24 24"
d="M0 0h24v24H0z" /> class="-mr-1 ml-2 h-5 w-5"
<path ><path fill="none" d="M0 0h24v24H0z" />
fill="currentColor" <path
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> fill="currentColor"
</button> d="M3 19h18v2H3v-2zm10-5.83l6.07-6.07 1.42 1.41L12 17 3.52 8.52l1.4-1.42L11 13.17V2h2v11.17z"
</div> /></svg
{#if sponsoring_contracts_download_open} >
<div </button>
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> </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 z-10"
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} {/if}

View File

@@ -1,7 +1,7 @@
<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 { import {
RunnerService, RunnerService,
RunnerTeamService, RunnerTeamService,
@@ -125,7 +125,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -3,7 +3,7 @@
import { read as readXlsx, utils as xlsx_utils } from "xlsx"; import { read as readXlsx, utils as xlsx_utils } from "xlsx";
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 Toastify from "toastify-js"; import Toastify from "toastify-js";
import { import {
ImportService, ImportService,
@@ -228,7 +228,7 @@
{#if import_modal_open} {#if import_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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
cancelModal(); cancelModal();

View File

@@ -71,6 +71,9 @@
}).showToast(); }).showToast();
let postdata = {}; let postdata = {};
postdata = Object.assign(postdata, editable); postdata = Object.assign(postdata, editable);
if (postdata.phone === "") {
postdata.phone = null;
}
RunnerService.runnerControllerPut(original_data.id, postdata) RunnerService.runnerControllerPut(original_data.id, postdata)
.then((resp) => { .then((resp) => {
Object.assign(original_data, editable); Object.assign(original_data, editable);
@@ -95,7 +98,7 @@
</script> </script>
{#await runner_promise} {#await runner_promise}
{$_('loading-runners')} {$_("loading-runners")}
{:then} {:then}
<section class="container p-5 select-none"> <section class="container p-5 select-none">
<div class="flex flex-row mb-4"> <div class="flex flex-row mb-4">
@@ -109,12 +112,15 @@
class="flex-shrink-0 w-5 h-5 mr-2" class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor" fill="currentColor"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg> d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"
/></svg
>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="./">{$_('runners')}</a><svg <a class="mr-2" href="./">{$_("runners")}</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -124,17 +130,17 @@
class="h-3 w-3 mr-2 stroke-current" class="h-3 w-3 mr-2 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><line xmlns="http://www.w3.org/2000/svg"
x1="5" ><line x1="5" y1="12" x2="19" y2="12" />
y1="12" <polyline points="12 5 19 12 12 19" /></svg
x2="19" >
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="mr-2">{original_data.firstname} <span class="mr-2"
{original_data.middlename || ''} >{original_data.firstname}
{original_data.lastname}</span> {original_data.middlename || ""}
{original_data.lastname}</span
>
</li> </li>
</ol> </ol>
</nav> </nav>
@@ -142,36 +148,42 @@
</div> </div>
<div class="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname} {original_data.firstname}
{original_data.middlename || ''} {original_data.middlename || ""}
{original_data.lastname} {original_data.lastname}
<span data-id="runner_actions_${editable.id}"> <span data-id="runner_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:DELETE")}
{#if delete_triggered} {#if delete_triggered}
<button <button
on:click={deleteRunner} on:click={deleteRunner}
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> 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
>
<button <button
on:click={() => { on:click={() => {
delete_triggered = !delete_triggered; delete_triggered = !delete_triggered;
}} }}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button> 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} {/if}
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_runners /> bind:generate_runners
<GenerateRunnerCards />
bind:cards_show <GenerateRunnerCards bind:cards_show bind:generate_runners />
bind:generate_runners />
<GenerateRunnerCertificates <GenerateRunnerCertificates
bind:certificates_show bind:certificates_show
bind:generate_runners /> bind:generate_runners
/>
{#if !delete_triggered} {#if !delete_triggered}
<button <button
on:click={() => { on:click={() => {
delete_triggered = true; delete_triggered = true;
}} }}
type="button" type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-runner')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("delete-runner")}</button
>
{/if} {/if}
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
@@ -180,121 +192,128 @@
class:opacity-50={!save_enabled} class:opacity-50={!save_enabled}
type="button" type="button"
on:click={submit} on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button> 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
>
{/if} {/if}
</span> </span>
</div> </div>
<!-- --> <!-- -->
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label for="firstname" class="font-medium text-gray-700"
for="firstname" >{$_("first-name")}</label
class="font-medium text-gray-700">{$_('first-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('first-name')} placeholder={$_("first-name")}
type="text" type="text"
class:border-red-500={!isFirstnameValid} class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid} class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid} class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname} bind:value={editable.firstname}
name="firstname" name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isFirstnameValid} {#if !isFirstnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('first-name-is-required')} >
{$_("first-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label for="middlename" class="font-medium text-gray-700"
for="middlename" >{$_("middle-name")}</label
class="font-medium text-gray-700">{$_('middle-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('middle-name')} placeholder={$_("middle-name")}
type="text" type="text"
bind:value={editable.middlename} bind:value={editable.middlename}
name="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" /> 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>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label for="lastname" class="font-medium text-gray-700"
for="lastname" >{$_("last-name")}</label
class="font-medium text-gray-700">{$_('last-name')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('last-name')} placeholder={$_("last-name")}
type="text" type="text"
bind:value={editable.lastname} bind:value={editable.lastname}
class:border-red-500={!isLastnameValid} class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid} class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid} class:focus:ring-red-500={!isLastnameValid}
name="lastname" 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" /> 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} {#if !isLastnameValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('last-name-is-required')} >
{$_("last-name-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label <label for="email" class="font-medium text-gray-700"
for="email" >{$_("e-mail-adress")}</label
class="font-medium text-gray-700">{$_('e-mail-adress')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('e-mail-adress')} placeholder={$_("e-mail-adress")}
type="email" type="email"
bind:value={editable.email} bind:value={editable.email}
class:border-red-500={!isEmailValid} class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid} class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid} class:focus:ring-red-500={!isEmailValid}
name="email" 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" /> 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} {#if !isEmailValid}
<span <span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"> class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
{$_('valid-email-is-required')} >
{$_("valid-email-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label> <label for="phone" class="font-medium text-gray-700">{$_("phone")}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('phone')} placeholder={$_("phone")}
type="tel" type="tel"
bind:value={editable.phone} bind:value={editable.phone}
name="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" /> 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>
<div class="text-sm w-full"> <div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('group')}</span> <span class="font-medium text-gray-700">{$_("group")}</span>
<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) => label itemFilter={(label, filterText, option) =>
.toLowerCase() label.toLowerCase().includes(filterText.toLowerCase()) ||
.includes( option.id.value.toString().startsWith(filterText.toLowerCase())}
filterText.toLowerCase()
) || option.id.value
.toString()
.startsWith(filterText.toLowerCase())}
items={groups} items={groups}
showChevron={true} showChevron={true}
placeholder={$_('search-for-an-organization-or-team-by-name-or-id')} placeholder={$_("search-for-an-organization-or-team-by-name-or-id")}
noOptionsMessage={$_('no-organization-or-team-found')} noOptionsMessage={$_("no-organization-or-team-found")}
bind:selectedValue={group} bind:selectedValue={group}
on:select={(selectedValue) => { on:select={(selectedValue) => {
editable.group = selectedValue.detail.value.id; editable.group = selectedValue.detail.value.id;
}} }}
on:clear={() => (editable.group = null)} /> on:clear={() => (editable.group = null)}
/>
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('distance')}</span> <span class="font-medium text-gray-700">{$_("distance")}</span>
<br /> <br />
<span class="text-gray-700">{original_data.distance /1000 } km</span> <span class="text-gray-700">{original_data.distance / 1000} km</span>
</div> </div>
</section> </section>
{:catch error} {:catch error}

View File

@@ -1,37 +1,72 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
let tablePageCount = [50, 100, 250, 500];
import { writable } from "svelte/store";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
} from "@tanstack/svelte-table";
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "firstname",
header: () => $_('first-name'),
filterFn: `includesString`,
},
{
accessorKey: "lastname",
header: () => $_('last-name'),
filterFn: `includesString`,
},
{
accessorKey: "group",
header: () => $_('group'),
cell: (info) => {
const group = info.getValue();
if(group.responseType === "RUNNERORGANIZATION"){return group.name}
return `${group.parentGroup.name} > ${group.name}`
},
filterFn: `includesString`,
},
{
accessorKey: "distance",
header: () => $_('distance'),
cell: (info) => {
if(info.getValue() < 1000){return `${info.getValue()} m`}
return `${(info.getValue() / 1000).toFixed(1)} km`
},
enableColumnFilter: false,
},
];
//
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 Select from "svelte-select";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte"; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
$: searchvalue = ""; import { onMount } from "svelte";
$: active_deletes = []; $: active_deletes = [];
let dataLoaded = false;
export let current_runners = []; export let current_runners = [];
const runners_promise = RunnerService.runnerControllerGetAll().then((val) => { $: sponsoring_contracts_show = generate_runners.length > 0;
current_runners = val; $: cards_show = generate_runners.length > 0;
}); $: certificates_show = generate_runners.length > 0;
$: selectedFilter_teams = null; $: generate_runners = []; //current_runners.filter((r) => r.selected === true);
$: selectedFilter = null;
$: filter__teams = selectedFilter_teams || [];
$: filter__orgs = selectedFilter || [];
$: filterGroupIDs = filter__teams.concat(filter__orgs).map((i) => i.value);
$: sponsoring_contracts_show = current_runners.some(
(r) => r.is_selected === true
);
$: cards_show = current_runners.some(
(r) => r.is_selected === true
);
$: certificates_show = current_runners.some(
(r) => r.is_selected === true
);
$: generate_runners = current_runners.filter((r) => r.is_selected === true);
$: teams = []; $: teams = [];
$: orgs = []; $: orgs = [];
$: mappedteams = teams.map(function (g) { $: mappedteams = teams.map(function (g) {
@@ -42,222 +77,219 @@
return { value: g.id, label: g.name }; return { value: g.id, label: g.name };
}) })
.concat(mappedteams); .concat(mappedteams);
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: tablePageCount[0],
},
},
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
onMount(() => {
RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val;
dataLoaded = true;
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { options.update((options) => ({
teams = val; ...options,
data: current_runners,
}));
});
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
orgs = val;
}
);
}); });
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")}
{#await runners_promise} {#if !dataLoaded}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"> role="alert"
<p class="font-bold">{$_('runners-are-being-loaded')}</p> >
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <p class="font-bold">{$_("runners-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:then} {:else}
{#if current_runners.length === 0} <div class="h-12">
<RunnersEmptyState /> <GenerateSponsoringContracts
{:else} bind:sponsoring_contracts_show
<input bind:generate_runners
type="search" />
bind:value={searchvalue} <GenerateRunnerCards bind:cards_show bind:generate_runners />
placeholder={$_('datatable.search')} <GenerateRunnerCertificates
aria-label={$_('datatable.search')} bind:certificates_show
class="gridjs-input gridjs-search-input mb-4" /> bind:generate_runners
<div class="block mb-6"> />
<label </div>
for="country" <table class="w-full">
class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label> <thead>
<Select {#each $table.getHeaderGroups() as headerGroup}
on:select={(event) => { <tr class="select-none">
selectedFilter = event.detail; {#each headerGroup.headers as header}
}} <th class="cursor-pointer">
selectedValue={selectedFilter} {#if !header.isPlaceholder}
placeholder={$_('filter-by-organization-team')} <div on:click={header.column.getToggleSortingHandler()}>
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" <svelte:component
items={selectgroups} this={flexRender(
isMulti={true} /> header.column.columnDef.header,
</div> header.getContext()
<div class="h-12"> )}
<GenerateSponsoringContracts />
bind:sponsoring_contracts_show {#if header.column.getIsSorted().toString() == "asc"}
bind:generate_runners /> 🔼
<GenerateRunnerCards {:else if header.column.getIsSorted().toString() == "desc"}
bind:cards_show 🔽
bind:generate_runners />
<GenerateRunnerCertificates
bind:certificates_show
bind:generate_runners />
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span
on:click={() => {
const newstate = !current_runners.some((r) => r.is_selected === true);
current_runners = current_runners.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)}
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact-information')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('group')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('distance-in-km')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_runners as runner}
{#if runner.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || runner.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(runner.id)}
{#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0}
<tr
data-rowid="user_{runner.id}"
data-groupid={runner.group.id}>
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={runner.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{runner.firstname}
{runner.middlename || ''}
{runner.lastname}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if runner.email}
<div class="text-sm text-gray-500">{runner.email}</div>
{/if}
{#if runner.phone}
<div class="text-sm text-gray-500">{runner.phone}</div>
{/if}
{#if runner.address.address1 !== null}
{runner.address.address1}<br />
{runner.address.address2 || ''}<br />
{runner.address.postalcode}
{runner.address.city}
{runner.address.country}
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if runner.group.responseType === 'RUNNERTEAM'}
<a
href="../teams/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.parentGroup.name} &gt; {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 /1000 } km
</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} {/if}
</tr> </div>
{/if} {/if}
{/if} {#if header.column.getCanFilter()}
<div class="relative max-w-xs">
<input
title="name-search"
value={header.column.getFilterValue() || ""}
on:keyup={(e) => {
header.column.setFilterValue(e.target.value);
}}
type="text"
class="block rounded-md border-gray-200 py-2 pl-8 text-xs focus:border-blue-500 focus:ring-blue-500"
placeholder=""
/>
<div
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2"
>
<svg
class="h-3.5 w-3.5 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
width={16}
height={16}
fill="currentColor"
viewBox="0 0 16 16"
>
<path
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
/>
</svg>
</div>
</div>
{/if}
</th>
{/each} {/each}
</tbody> </tr>
</table> {/each}
</div> </thead>
{/if} <tbody>
{:catch error} {#each $table.getRowModel().rows as row}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <tr>
<span class="inline-block align-middle mr-8"> {#each row.getVisibleCells() as cell}
<b class="capitalize">{$_('general_promise_error')}</b> <td>
{error} <svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
<div class="h-2" />
<div class="flex items-center gap-2">
<button
class="border rounded p-1"
on:click={() => $table.setPageIndex(0)}
disabled={!$table.getCanPreviousPage()}
>
{"<<"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.previousPage()}
disabled={!$table.getCanPreviousPage()}
>
{"<"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.nextPage()}
disabled={!$table.getCanNextPage()}
>
{">"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.setPageIndex($table.getPageCount() - 1)}
disabled={!$table.getCanNextPage()}
>
{">>"}
</button>
<span class="flex items-center gap-1">
<div>Page</div>
<strong>
{$table.getState().pagination.pageIndex + 1} of{" "}
{$table.getPageCount()}
</strong>
</span> </span>
<span class="flex items-center gap-1">
| Go to page:
<input
type="number"
defaultValue={$table.getState().pagination.pageIndex + 1}
on:change={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
$table.setPageIndex(page);
}}
class="border p-1 rounded w-16"
/>
</span>
<select
value={$table.getState().pagination.pageSize}
on:change={(e) => {
const ps = Number(e.target.value);
console.log({ ps });
$table.setPageSize(Number(e.target.value));
}}
>
{#each tablePageCount as pageSize}
<option value={pageSize}>{pageSize}</option>
{/each}
</select>
</div> </div>
{/await} <pre>{JSON.stringify($table.getState(), null, 2)}</pre>
{/if}
{/if} {/if}
<style>
thead {
background: #fff;
}
thead {
position: sticky;
inset-block-start: 0;
}
tbody td {
padding: 4px;
}
tbody tr:nth-child(even) {
background: #fafafa;
}
tbody tr {
transition: all, 0.2s;
}
tbody tr:hover {
background: #f5f5f5;
}
</style>

View File

@@ -0,0 +1,35 @@
<script>
import { _ } from "svelte-i18n";
export let groups;
export let handler;
let selected = "all";
</script>
<th style="border-bottom: 1px solid #ddd;">
<select
on:input={() => {
setTimeout(() => {
if (`${selected}`.trim()) {
const value = selected;
handler.filter(value, (runner) => {
if (
runner.group.id === value ||
runner?.group?.parentGroup?.id === value ||
value === "all"
)
return runner;
return "";
});
}
}, 50);
}}
bind:value={selected}
name="groupfilter"
id="groupfilter"
>
<option value="all">{$_('all')}</option>
{#each groups as g}
<option value={g.value}>{g.label}</option>
{/each}
</select>
</th>

View File

@@ -1,7 +1,7 @@
<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 { import {
RunnerService, RunnerService,
ScanService, ScanService,
@@ -14,7 +14,7 @@
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.id.toString().startsWith(filterText.toLowerCase());
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
@@ -81,7 +81,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,125 +1,147 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { import { DataHandler, Datatable, Th, ThFilter } from "@vincjo/datatables";
ScanService, import { ScanService, TrackService } from "@odit/lfk-client-js";
} from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import ScansEmptyState from "./ScansEmptyState.svelte"; import ScansEmptyState from "./ScansEmptyState.svelte";
$: searchvalue = ""; import ThFilterRunner from "./ThFilterRunner.svelte";
import ThFilterTrack from "./ThFilterTrack.svelte";
$: active_deletes = []; $: active_deletes = [];
export let current_scans = []; export let current_scans = [];
const handler = new DataHandler(current_scans, { rowsPerPage: 20 });
const rows = handler.getRows();
const scans_promise = ScanService.scanControllerGetAll().then((val) => { const scans_promise = ScanService.scanControllerGetAll().then((val) => {
current_scans = val; current_scans = val;
handler.setRows(val);
}); });
function should_display_based_on_id(id) { $: allTracks = [];
if (searchvalue.toString().slice(-1) === "*") { TrackService.trackControllerGetAll().then((val) => {
return id.toString().startsWith(searchvalue.replace("*", "")); allTracks = val;
});
function format_laptime(laptime) {
if (laptime == 0 || laptime == null) {
return $_("first-scan-of-the-day");
} }
return id.toString() === searchvalue; if (laptime < 60) {
} return `${laptime}s`;
function format_laptime(laptime){ }
if(laptime == 0 || laptime == null){return $_('first-scan-of-the-day')} if (laptime < 3600) {
if(laptime < 60){return `${laptime}s`} return `${Math.floor(laptime / 60)}min ${
if(laptime < 3600){return `${Math.floor(laptime / 60)}min ${laptime - (Math.floor(laptime / 60)*60)}s`} laptime - Math.floor(laptime / 60) * 60
return `${Math.floor(laptime / 3600)}h ${laptime - (Math.floor(laptime / 3600)*3600)}min ${laptime - (Math.floor(laptime / 3600)*3600) - (Math.floor(laptime / 60)*60)}` }s`;
}
return `${Math.floor(laptime / 3600)}h ${
laptime - Math.floor(laptime / 3600) * 3600
}min ${
laptime -
Math.floor(laptime / 3600) * 3600 -
Math.floor(laptime / 60) * 60
}`;
} }
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
{#await scans_promise} {#await scans_promise}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"> role="alert"
<p class="font-bold">{$_('scans-are-being-loaded')}</p> >
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <p class="font-bold">{$_("scans-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:then} {:then}
{#if current_scans.length === 0} {#if current_scans.length === 0}
<ScansEmptyState /> <ScansEmptyState />
{:else} {:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div <div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
<table class="divide-y divide-gray-200 w-full"> >
<thead class="bg-gray-50"> <Datatable {handler}>
<tr> <table class="divide-y divide-gray-200 w-full">
<th <thead class="bg-gray-50">
scope="col" <tr>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <Th {handler} orderBy="id">ID</Th>
{$_('runner')} <Th {handler}>
</th> {$_("runner")}
<th </Th>
scope="col" <Th {handler}>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> {$_("distance")}
{$_('distance-track')} </Th>
</th> <Th {handler}>
<th {$_("track")}
scope="col" </Th>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <Th {handler}>
{$_('laptime')} {$_("laptime")}
</th> </Th>
<th <Th {handler}>
scope="col" {$_("status")}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </Th>
{$_('status')} <th
</th> scope="col"
<th scope="col" class="relative px-6 py-3"> class="relative px-6 py-3"
<span class="sr-only">{$_('action')}</span> style="border-bottom: 1px solid #ddd;"
</th> >
</tr> {$_("action")}
</thead> </th>
<tbody class="divide-y divide-gray-200"> </tr>
{#each current_scans as scan} <tr>
{#if scan.track?.name <ThFilter {handler} filterBy="id" />
.toLowerCase() <ThFilterRunner {handler} />
.includes( <th style="border-bottom: 1px solid #ddd;" />
searchvalue.toLowerCase() <ThFilterTrack tracks={allTracks} {handler} />
) || scan.runner?.firstname <!-- <th style="border-bottom: 1px solid #ddd;" /> -->
.toLowerCase() <th style="border-bottom: 1px solid #ddd;" />
.includes( <th style="border-bottom: 1px solid #ddd;" />
searchvalue.toLowerCase() <!-- TODO: filter status -->
) || scan.runner?.lastname <th style="border-bottom: 1px solid #ddd;" />
.toLowerCase() </tr>
.includes( </thead>
searchvalue.toLowerCase() <tbody class="divide-y divide-gray-200">
) || should_display_based_on_id(scan.id)} {#each $rows as scan}
<tr data-rowid="scan_{scan.id}"> <tr data-rowid="scan_{scan.id}">
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{scan.id}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center"> <div class="flex items-center">
<a <a
href="../runners/{scan.runner.id}" href="../runners/{scan.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.runner.firstname} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
{scan.runner.middlename || ''} >{scan.runner.firstname}
{scan.runner.lastname}</a> {scan.runner.middlename || ""}
{scan.runner.lastname}</a
>
</div> </div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{#if scan.distance < 1000} {#if scan.distance < 1000}
{scan.distance}m {scan.distance}m
{:else}{scan.distance / 1000}km{/if} {:else}{scan.distance / 1000}km{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{#if scan.track} {#if scan.track}
<a <a
href="../tracks" href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.track.name} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{scan.track.name}
</a> </a>
{/if} {/if}
</div> </div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap text-left">
{#if scan.responseType === "TRACKSCAN"} {#if scan.responseType === "TRACKSCAN"}
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{format_laptime(scan.lapTime)} {format_laptime(scan.lapTime)}
</div> </div>
{:else} {:else}
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{$_('scan-with-fixed-distance')} {$_("scan-with-fixed-distance")}
</div> </div>
{/if} {/if}
</td> </td>
@@ -127,23 +149,30 @@
<div class="flex items-center"> <div class="flex items-center">
{#if scan.valid} {#if scan.valid}
<span <span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('valid')}</span> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("valid")}</span
>
{:else} {:else}
<span <span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('invalid')}</span> class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("invalid")}</span
>
{/if} {/if}
</div> </div>
</td> </td>
{#if active_deletes[scan.id] === true} {#if active_deletes[scan.id] === true}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button <button
on:click={() => { on:click={() => {
active_deletes[scan.id] = false; active_deletes[scan.id] = false;
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button> class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button <button
on:click={() => { on:click={() => {
ScanService.scanControllerRemove(scan.id, false).then( ScanService.scanControllerRemove(scan.id, false).then(
@@ -152,44 +181,51 @@
(obj) => obj.id !== scan.id (obj) => obj.id !== scan.id
); );
Toastify({ Toastify({
text: 'Scan deleted', text: "Scan deleted",
duration: 500, duration: 500,
backgroundColor: backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)', "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
} }
); );
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td> </td>
{:else} {:else}
<td <td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a <a
href="./{scan.id}" href="./{scan.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a> class="text-indigo-600 hover:text-indigo-900"
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:DELETE')} >{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:DELETE")}
<button <button
on:click={() => { on:click={() => {
active_deletes[scan.id] = true; active_deletes[scan.id] = true;
}} }}
tabindex="0" tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button> class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if} {/if}
</td> </td>
{/if} {/if}
</tr> </tr>
{/if} {/each}
{/each} </tbody>
</tbody> </table>
</table> </Datatable>
</div> </div>
{/if} {/if}
{:catch error} {:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <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"> <span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b> <b class="capitalize">{$_("general_promise_error")}</b>
{error} {error}
</span> </span>
</div> </div>

View File

@@ -0,0 +1,50 @@
<script>
export let handler;
let filterValue = "";
</script>
<th>
<input
on:input={() => {
setTimeout(() => {
const v = filterValue.toLowerCase();
handler.filter(v, (c) => {
if (v.startsWith("#")) {
return `#${c.runner?.id}`;
}
if (c.runner) {
let runnerName = `${c.runner.firstname} ${c.runner.lastname}`;
if (c.runner.middlename) {
runnerName = `${c.runner.firstname} ${c.runner.middlename} ${c.runner.lastname}`;
}
runnerName = runnerName.toLowerCase();
return runnerName;
}
return "";
});
}, 150);
}}
placeholder="Filter"
bind:value={filterValue}
type="text"
name="runnerfilter"
id="runnerfilter"
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
margin: -1px 0 0 0;
padding: 0;
width: 100%;
height: 24px;
border: none;
text-align: left;
background: inherit;
outline: 0;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,31 @@
<script>
import { _ } from "svelte-i18n";
export let tracks;
export let handler;
let selected = "all";
</script>
<th style="border-bottom: 1px solid #ddd;">
<select
on:input={() => {
setTimeout(() => {
if (`${selected}`.trim()) {
const value = selected;
handler.filter(value, (scan) => {
// TODO: fix filter
if (scan.track.id === value || value === "all") return scan.track.id;
return "";
});
}
}, 50);
}}
bind:value={selected}
name="trackfilter"
id="trackfilter"
>
<option value="all">{$_("all")}</option>
{#each tracks as track}
<option value={track.id}>{track.name}</option>
{/each}
</select>
</th>

View File

@@ -1,7 +1,7 @@
<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 { ScanStationService, TrackService } from "@odit/lfk-client-js"; import { ScanStationService, TrackService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import Select from "svelte-select"; import Select from "svelte-select";
@@ -81,7 +81,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { ScanStationService } from "@odit/lfk-client-js"; import { ScanStationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@@ -32,7 +32,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={cancelDelete}> on:click_outside={cancelDelete}>
<div <div

View File

@@ -1,6 +1,6 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { tick, createEventDispatcher } from "svelte"; import { tick, createEventDispatcher } from "svelte";
export let copy_modal_open; export let copy_modal_open;
@@ -46,7 +46,7 @@
{#if valueCopy != null} {#if valueCopy != null}
<textarea bind:this={areaDom}>{valueCopy}</textarea> <textarea bind:this={areaDom}>{valueCopy}</textarea>
{/if} {/if}
<div class="fixed z-10 inset-0 overflow-y-auto" use:focusTrap> <div class="fixed z-10 inset-0 overflow-y-auto" >
<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">

View File

@@ -1,7 +1,7 @@
<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 { MeService } from "@odit/lfk-client-js"; import { MeService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@@ -30,7 +30,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={cancelDelete}> on:click_outside={cancelDelete}>
<div <div

View File

@@ -0,0 +1,151 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { StatsClientService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let modal_open;
export let new_client;
export let current_clients;
export let copy_modal_open;
function focus(el) {
el.focus();
}
$: description = "";
$: createbtnenabled = description != "";
$: processed_last_submit = true;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("statsclient-is-being-added"),
duration: -1,
}).showToast();
StatsClientService.statsClientControllerPost({description})
.then((result) => {
description = "";
modal_open = false;
//
Toastify({
text: $_("scanstation-added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_clients.push(result);
current_clients = current_clients;
new_client = result;
copy_modal_open = true;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-statsclient')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('please-provide-the-required-information-to-create-a-new-statsclient')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="description"
class="block text-sm font-medium text-gray-700">{$_('description')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_('description')}
bind:value={description}
type="text"
name="description"
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>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,92 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { ScanStationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let delete_station;
const dispatch = createEventDispatcher();
function cancelDelete() {
modal_open = false;
dispatch("cancelDelete", { id: delete_station.id });
}
function deleteClient() {
ScanStationService.donorControllerRemove(
delete_station.id,
true
)
.then((resp) => {
Toastify({
text: $_('statsclient-deleted'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {});
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={cancelDelete}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-blue-600" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"/></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">
{$_('attention')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
'do-you-want-to-delete-this-donor-with-all-related-donations'
)}
<br />
{$_('all-associated-scans-will-get-deleted-as-well')}
</p>
</div>
</div> -->
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={deleteClient}
type="button"
class="w-full inline-flex 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-statsclient')}
</button>
<button
on:click={cancelDelete}
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-keep-statsclient')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,129 @@
<script>
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import { tick, createEventDispatcher } from "svelte";
export let copy_modal_open;
export let new_client;
const dispatch = createEventDispatcher();
let valueCopy = null;
let areaDom;
let copied = false;
function close() {
copy_modal_open = false;
}
async function copy() {
valueCopy = new_client.key;
await tick();
areaDom.focus();
areaDom.select();
try {
const successful = document.execCommand("copy");
if (!successful) {
throw new Error();
}
Toastify({
text: $_("copied-token-to-clipboard"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
copied = true;
} catch (err) {
Toastify({
text: $_("error-whyile-copying-to-clipboard"),
duration: 500,
backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
}).showToast();
}
// we can notifi by event or storage about copy status
valueCopy = null;
}
</script>
{#if copy_modal_open}
{#if valueCopy != null}
<textarea bind:this={areaDom}>{valueCopy}</textarea>
{/if}
<div class="fixed z-10 inset-0 overflow-y-auto" >
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></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">
{$_('token')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('the-statsclient-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')}
<br />
{$_('please-copy-the-token-and-store-it-somewhere-save')}
</p>
</div>
<div class="mt-2 mb-6">
<label
for="token"
class="block text-sm font-medium text-gray-700">{$_('token')}</label>
<div on:click={copy} class="inline-flex">
<p
name="token"
class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2">
{new_client.key}
</p>
<div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg>
</div>
</div>
<p class="text-gray-500 text-xs">
{$_('click-to-copy-token-to-clipboard')}
</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={close}
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-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('yes-i-copied-the-token')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,119 @@
<script>
import { t, _ } from "svelte-i18n";
import store from "../../store";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import ConfirmStatsClientDeletion from "./ConfirmStatsClientDeletion.svelte";
import { StatsClientService } from "@odit/lfk-client-js";
let data_loaded = false;
let modal_open;
let delete_client;
export let params;
$: delete_triggered = false;
$: original_data = {};
const promise = StatsClientService.statsClientControllerGetOne(
params.clientid
).then((data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
});
function deleteClient() {
StatsClientService.statsClientControllerRemove(original_data.id, false)
.then((resp) => {
Toastify({
text: $_("statsclient-deleted"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_client = original_data;
});
}
</script>
<ConfirmStatsClientDeletion bind:modal_open bind:delete_client />
{#await promise}
{$_('loading-statsclient-details')}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
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
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg>
</li>
<li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_('statsclient')}</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">#{original_data.id}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
#{original_data.id}
<span data-id="stations_actions_${original_data.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:DELETE')}
{#if delete_triggered}
<button
on:click={deleteClient}
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>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-statsclient')}</button>
{/if}
{/if}
</span>
</div>
<!-- -->
<div class="text-sm w-full">
<label
for="description"
class="font-medium text-gray-700">{$_('description')}</label>
<p
name="description"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" >
{original_data.description}</p>
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

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

View File

@@ -0,0 +1,21 @@
<script>
import { _ } from "svelte-i18n";
import AddStatsClientModal from "./AddStatsClientModal.svelte";
import CopyScanStationTokenModal from "./CopyStatsClientTokenModal.svelte";
import scanstations_empty from "./statsclients_empty.svg";
let modal_open = false;
let copy_modal_open = false;
let new_client = {};
let current_clients = [];
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={scanstations_empty} alt="" />
<span class="font-bold">{$_('you-dont-have-any-scanclients-yet')}.</span><br />
<span>{$_('add-the-first-statsclient')}</span>
</p>
</div>
<AddStatsClientModal bind:modal_open bind:current_clients bind:new_client bind:copy_modal_open/>
<CopyScanStationTokenModal bind:copy_modal_open bind:new_client />

View File

@@ -0,0 +1,150 @@
<script>
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import { StatsClientService } from "@odit/lfk-client-js";
const promise = StatsClientService.statsClientControllerGetAll().then(
(result) => {
current_clients = result;
}
);
import store from "../../store";
import StatsClientsEmptyState from "./StatsClientsEmptyState.svelte";
import ConfirmStatsClientDeletion from "./ConfirmStatsClientDeletion.svelte";
$: searchvalue = "";
$: active_deletes = [];
let delete_client = {};
let modal_open = false;
export let current_clients = [];
</script>
<ConfirmStatsClientDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_client />
{#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:GET')}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('statsclients-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_clients.length === 0}
<StatsClientsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('description')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('prefix')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_clients as c}
{#if Object.values(c)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="station_{c.id}">
<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">
{c.description}
</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">
{c.prefix}
</div>
</div>
</div>
</td>
{#if active_deletes[c.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[c.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
StatsClientService.statsClientControllerRemove(c.id, false)
.then((resp) => {
current_clients = current_clients.filter((obj) => obj.id !== c.id);
Toastify({
text: $_('statsclient-deleted'),
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_client = c;
});
}}
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="/statsclients/{c.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:DELETE')}
<button
on:click={() => {
active_deletes[c.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}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,7 +1,7 @@
<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 { import {
RunnerOrganizationService, RunnerOrganizationService,
RunnerTeamService, RunnerTeamService,
@@ -77,7 +77,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { RunnerTeamService } from "@odit/lfk-client-js"; import { RunnerTeamService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@@ -29,7 +29,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={cancelDelete}> on:click_outside={cancelDelete}>
<div <div

View File

@@ -1,7 +1,7 @@
<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 { tracks as tracksstore } from "../../store.js"; import { tracks as tracksstore } from "../../store.js";
import { TrackService } from "@odit/lfk-client-js"; import { TrackService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
@@ -75,7 +75,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,7 +1,7 @@
<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 { 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";
@@ -92,7 +92,7 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -82,14 +82,6 @@
<tr data-rowid="user_{u.id}"> <tr data-rowid="user_{u.id}">
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center"> <div class="flex items-center">
{#if u.profilePic}
<div class="flex-shrink-0 h-10 w-10">
<img
class="h-10 w-10 rounded-full"
src={u.profilePic}
alt="" />
</div>
{/if}
<div class="ml-4"> <div class="ml-4">
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{u.firstname} {u.firstname}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
module.exports = { module.exports = {
mode: 'jit', mode: 'jit',
purge: [ './src/**/*.svelte' ], content: [ './src/**/*.svelte' ],
theme: { theme: {
extend: { extend: {
colors: { colors: {

View File

@@ -1,38 +1,11 @@
import svelte from '@sveltejs/vite-plugin-svelte'; // vite.config.js
import { minify } from 'html-minifier';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
// import { svelte } from '@sveltejs/vite-plugin-svelte';
const indexReplace = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
return minify(html, {
collapseWhitespace: true
});
}
};
};
export default defineConfig(({ command, mode }) => { export default defineConfig({
const isProduction = mode === 'production'; plugins: [
return { svelte({
// base: './', /* plugin options */
build: { })
polyfillDynamicImport: false, ]
cssCodeSplit: false, });
minify: isProduction
},
plugins: [
svelte({
//@ts-ignore
hot: !isProduction,
emitCss: true,
extensions: [ '.md', '.svx', '.svelte' ],
preprocess: [
//
]
}),
indexReplace()
]
};
});