Compare commits

..

114 Commits

Author SHA1 Message Date
845737ee8e Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 16:18:55 +02:00
6993511c67 feat(RunnersOverview): row selection 2023-04-12 16:18:45 +02:00
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
58 changed files with 7512 additions and 2938 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
@@ -51,10 +62,8 @@ steps:
password: password:
from_secret: docker_password from_secret: docker_password
build_args: build_args:
- NPM_REGISTRY_DOMAIN: - NPM_REGISTRY_URL:
from_secret: npmjs_domain from_secret: npm_url
- NPM_REGISTRY_TOKEN:
from_secret: npmjs_token
repo: lfk/frontend repo: lfk/frontend
tags: tags:
- dev - dev
@@ -80,10 +89,8 @@ steps:
password: password:
from_secret: docker_password from_secret: docker_password
build_args: build_args:
- NPM_REGISTRY_DOMAIN: - NPM_REGISTRY_URL:
from_secret: npmjs_domain from_secret: npm_url
- NPM_REGISTRY_TOKEN:
from_secret: npmjs_token
repo: lfk/frontend repo: lfk/frontend
tags: tags:
- "${DRONE_TAG}" - "${DRONE_TAG}"

7
.gitignore vendored
View File

@@ -1,11 +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
pnpm-lock.yaml

View File

@@ -2,9 +2,120 @@
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) #### [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) - 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) #### [0.16.0](https://git.odit.services/lfk/frontend/compare/0.15.6...0.16.0)
@@ -1218,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,11 +1,14 @@
FROM registry.odit.services/hub/library/node:19.5.0-alpine3.16 as build 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 npx pnpm i COPY package.json pnpm-lock.yaml *.config.js *.config.cjs index.html ./
COPY package.json *.config.js postcss.config.cjs tailwind.config.js vite.config.js 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/library/nginx-brotli:3.15 as final FROM registry.odit.services/library/nginx-brotli:3.15 as final
COPY --from=build /app/dist /usr/share/nginx/html COPY --from=build /app/dist /usr/share/nginx/html

View File

@@ -13,7 +13,7 @@
</head> </head>
<body> <body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.16.1-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.16.1", "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.13.1", "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": "3.2.4", "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

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

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

@@ -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,39 +1,76 @@
<script> <script>
import { _ } 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";
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
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";
let pageLoaded = false; import { onMount } from "svelte";
$: searchvalue = ""; import InputElement from "../shared/InputElement.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;
pageLoaded = true; $: certificates_show = generate_runners.length > 0;
}); $: generate_runners = []; //current_runners.filter((r) => r.selected === true);
$: selectedFilter_teams = null;
$: 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) {
@@ -44,224 +81,239 @@
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],
},
},
enableRowSelection: true,
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; <th class="inset-y-0 left-0 px-4 py-2 text-left bg-stone-100 w-px">
}} <InputElement
selectedValue={selectedFilter} type="checkbox"
placeholder={$_('filter-by-organization-team')} checked={$table.getIsAllRowsSelected()}
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" indeterminate={$table.getIsSomeRowsSelected()}
items={selectgroups} on:change={() => $table.toggleAllRowsSelected()}
isMulti={true} /> />
</div> </th>
<div class="h-12"> {#each headerGroup.headers as header}
<GenerateSponsoringContracts <th class="cursor-pointer">
bind:sponsoring_contracts_show {#if !header.isPlaceholder}
bind:generate_runners /> <div on:click={header.column.getToggleSortingHandler()}>
<GenerateRunnerCards <svelte:component
bind:cards_show this={flexRender(
bind:generate_runners /> header.column.columnDef.header,
<GenerateRunnerCertificates header.getContext()
bind:certificates_show )}
bind:generate_runners /> />
</div> {#if header.column.getIsSorted().toString() == "asc"}
<div 🔼
class:hidden={!pageLoaded} {:else if header.column.getIsSorted().toString() == "desc"}
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll" 🔽
>
<table class="divide-y divide-gray-200 w-full table-fixed">
<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"> <td class="inset-y-0 left-0 px-4 py-2">
<b class="capitalize">{$_('general_promise_error')}</b> <InputElement
{error} type="checkbox"
checked={row.getIsSelected()}
on:change={() => row.toggleSelected()}
/>
</td>
{#each row.getVisibleCells() as cell}
<td>
<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>
<div>
{Object.keys(selected).length} of{" "}
{$table.getPreFilteredRowModel().rows.length} Total Rows Selected
</div>
{/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,20 @@
<script>
let className = "";
export { className as class };
export let type;
</script>
<input
class={`border-1 border-stone-300 border rounded-md shadow ${className} ${
type === "checkbox" && "w-5 h-5 text-orange-400"
}`}
{type}
{...$$restProps}
on:click
on:change
on:keydown
on:keyup
on:mouseenter
on:mouseleave
/>

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 { StatsClientService } from "@odit/lfk-client-js"; import { StatsClientService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
export let modal_open; export let modal_open;
@@ -66,7 +66,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 { 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,476 +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-or-update-a-payment": "Zahlung hinzufügen oder bearbeiten", "add-or-update-a-payment": "Zahlung hinzufügen oder bearbeiten",
"add-scan": "Scan erstellen", "add-scan": "Scan erstellen",
"add-the-first-scanstation": "Erstelle deine erste Scannerstation.", "add-the-first-scanstation": "Erstelle deine erste Scannerstation.",
"add-the-first-statsclient": "Erstelle deinen ersten Statsclient.", "add-the-first-statsclient": "Erstelle deinen ersten Statsclient.",
"add-user-group": "Neue Gruppe erstellen", "add-user-group": "Neue Gruppe erstellen",
"add-your-first-card": "Erstelle deine erste Läuferkarte", "add-your-first-card": "Erstelle deine erste Läuferkarte",
"add-your-first-contact": "Erstelle den ersten Kontakt", "add-your-first-contact": "Erstelle den ersten Kontakt",
"add-your-first-donor": "Erstelle die erste Sponsor:in", "add-your-first-donor": "Erstelle die erste Sponsor:in",
"add-your-first-group": "Erstelle die erste Gruppe", "add-your-first-group": "Erstelle die erste Gruppe",
"add-your-first-organization": "Erstelle die erste Organisation", "add-your-first-organization": "Erstelle die erste Organisation",
"add-your-first-runner": "Erstelle die erste Läufer:in", "add-your-first-runner": "Erstelle die erste Läufer:in",
"add-your-first-team": "Erstelle das erste Team", "add-your-first-team": "Erstelle das erste Team",
"add-your-first-track": "Erstelle den ersten Track (Laufstrecke).", "add-your-first-track": "Erstelle den ersten Track (Laufstrecke).",
"add-your-first-user": "Erstelle die erste Benutzer:in", "add-your-first-user": "Erstelle die erste Benutzer:in",
"add-your-fist-donation": "Erstelle dein erstes Sponsoring", "add-your-fist-donation": "Erstelle dein erstes Sponsoring",
"add-your-fist-scan": "Füge deinen ersten Scan hinzu", "add-your-fist-scan": "Füge deinen ersten Scan hinzu",
"adding-card": "Karte wird erstellt", "adding-card": "Karte wird erstellt",
"adding-donation": "Sponsoring wird erstellt...", "adding-donation": "Sponsoring wird erstellt...",
"adding-scan": "Scan wird hinzugefügt", "adding-scan": "Scan wird hinzugefügt",
"address": "Adresse", "address": "Adresse",
"address-is-required": "Du musst eine Adresse angeben", "address-is-required": "Du musst eine Adresse angeben",
"after-deletion-we-cant-restore-your-old-profile": "Nach der Löschung können auch die Admins dein Profil nicht wiederherstellen!", "after-deletion-we-cant-restore-your-old-profile": "Nach der Löschung können auch die Admins dein Profil nicht wiederherstellen!",
"after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "Nach der Änderung wirst du abgemeldet - bitte melde dich dann mit deinem neuen Passwort an.", "after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "Nach der Änderung wirst du abgemeldet - bitte melde dich dann mit deinem neuen Passwort an.",
"all-associated-donations-will-get-deleted-as-well": "Alle Sponsorings dieser Sponsor:in werden ebenfalls gelöscht", "all": "Alle",
"all-associated-runners-will-be-deleted-too": "Alle zugehörigen Läufer:innen werden auch gelöscht!", "all-associated-donations-will-get-deleted-as-well": "Alle Sponsorings dieser Sponsor:in werden ebenfalls gelöscht",
"all-associated-teams-and-runners-will-be-deleted-too": "Alle assoziierten Teams und Läufer:innen werden auch gelöscht!", "all-associated-runners-will-be-deleted-too": "Alle zugehörigen Läufer:innen werden auch gelöscht!",
"already-paid": "Bereits bezahlt", "all-associated-teams-and-runners-will-be-deleted-too": "Alle assoziierten Teams und Läufer:innen werden auch gelöscht!",
"amount": "Anzahl", "already-paid": "Bereits bezahlt",
"amount-per-kilometer": "Betrag pro Kilometer", "amount": "Anzahl",
"apartment-suite-etc": "Apartment, Wohnung, etc.", "amount-per-kilometer": "Betrag pro Kilometer",
"application_name": "Lauf für Kaya! - Admin", "apartment-suite-etc": "Apartment, Wohnung, etc.",
"applying-changes": "Änderungen anwenden", "application_name": "Lauf für Kaya! - Admin",
"attention": "Achtung!", "applying-changes": "Änderungen anwenden",
"author": "Autor:in", "attention": "Achtung!",
"bitte-bestaetige-diese-laeufer-fuer-den-import": "Bitte die Läufer:innen für den Import bestätigen.", "author": "Autor:in",
"by": "von", "bitte-bestaetige-diese-laeufer-fuer-den-import": "Bitte die Läufer:innen für den Import bestätigen.",
"cancel": "Abbrechen", "by": "von",
"cancel-delete": "Löschen abbrechen", "cancel": "Abbrechen",
"cancel-keep-donor": "Abbrechen, Sponsor:in behalten", "cancel-delete": "Löschen abbrechen",
"cancel-keep-my-profile": "Abbrechen, mein Profil behalten", "cancel-keep-donor": "Abbrechen, Sponsor:in behalten",
"cancel-keep-organization": "Abbrechen und Organisation bearbeiten", "cancel-keep-my-profile": "Abbrechen, mein Profil behalten",
"cancel-keep-statsclient": "Abbrechen und Statsclient behalten", "cancel-keep-organization": "Abbrechen und Organisation bearbeiten",
"cancel-keep-team": "Abbrechen, Team behalten", "cancel-keep-statsclient": "Abbrechen und Statsclient 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-team": "Abbrechen, Team behalten",
"card-added": "Karte wurde hinzugefügt", "cannot-reset-your-password-directly": "Schade. \nWir können das Passwort leider nicht direkt zurücksetzen.\nBitte sende uns eine Mail in der du deine Identität bestätigst.",
"card-deleted": "Karte gelöscht", "card-added": "Karte wurde hinzugefügt",
"card-updated": "Karte aktualisiert", "card-deleted": "Karte gelöscht",
"cards": "Läuferkarten", "card-updated": "Karte aktualisiert",
"certificates": "Urkunden", "cards": "Läuferkarten",
"change-your-password-here": "Hier kannst du dein Passwort ändern", "certificates": "Urkunden",
"changing-your-password": "Passwort wird geändert", "change-your-password-here": "Hier kannst du dein Passwort ändern",
"city": "Stadt", "changing-your-password": "Passwort wird geändert",
"click-to-copy-the-link-into-your-clipboard": "Klicke auf den Link, um ihn in deine Zwischenablage zu kopieren", "city": "Stadt",
"click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren", "click-to-copy-the-link-into-your-clipboard": "Klicke auf den Link, um ihn in deine Zwischenablage zu kopieren",
"close": "Schließen", "click-to-copy-token-to-clipboard": "Klicke auf den Token, um ihn in deine Zwischenablage zu kopieren",
"code": "Code", "close": "Schließen",
"configure-the-tracks-and-minimum-lap-times": "Bearbeite die Tracks und ihre minimale Rundenzeit", "code": "Code",
"confirm": "Bestätigen", "configure-the-tracks-and-minimum-lap-times": "Bearbeite die Tracks und ihre minimale Rundenzeit",
"confirm-delete": "Löschung Bestätigen", "confirm": "Bestätigen",
"confirm-delete-donor-with-all-donations": "Bestätigen, Sponsor:in mit allen Sponsorings löschen", "confirm-delete": "Löschung Bestätigen",
"confirm-delete-my-user-profile": "Bestätigung, mein Benutzerprofil löschen", "confirm-delete-donor-with-all-donations": "Bestätigen, Sponsor:in mit allen Sponsorings löschen",
"confirm-delete-organization-and-associated-teams-runners": "Bestätugung, lösche die Organisation und alle zugehörigen Teams und Läufer:innen.", "confirm-delete-my-user-profile": "Bestätigung, mein Benutzerprofil löschen",
"confirm-delete-statsclient": "Bestätigung, Statsclient löschen", "confirm-delete-organization-and-associated-teams-runners": "Bestätugung, lösche die Organisation und alle zugehörigen Teams und Läufer:innen.",
"confirm-delete-team-and-associated-runners": "Bestätigung, lösche das Team mitsamt seinen Läufer:innen.", "confirm-delete-statsclient": "Bestätigung, Statsclient löschen",
"confirm-deletion": "Löschung Bestätigen", "confirm-delete-team-and-associated-runners": "Bestätigung, lösche das Team mitsamt seinen Läufer:innen.",
"confirm-the-new-password": "Neues Passwort bestätigen", "confirm-deletion": "Löschung Bestätigen",
"contact": "Kontakt", "confirm-the-new-password": "Neues Passwort bestätigen",
"contact-added": "Kontakt wurde hinzugefügt", "contact": "Kontakt",
"contact-deleted": "Kontakt gelöscht", "contact-added": "Kontakt wurde hinzugefügt",
"contact-information": "Kontaktinformation", "contact-deleted": "Kontakt gelöscht",
"contact-is-being-added": "Kontakt wird erstellt...", "contact-information": "Kontaktinformation",
"contact-is-being-updated": "Kontakt wird aktualisiert ...", "contact-is-being-added": "Kontakt wird erstellt...",
"contact-is-not-a-member-in-any-group": "Kontakt gehört zu keiner Gruppe", "contact-is-being-updated": "Kontakt wird aktualisiert ...",
"contacts": "Kontakte", "contact-is-not-a-member-in-any-group": "Kontakt gehört zu keiner Gruppe",
"contacts-are-being-loaded": "Kontakte werden geladen ...", "contacts": "Kontakte",
"copied-link-to-clipboard": "Link wurde in die Zwischenablage kopiert", "contacts-are-being-loaded": "Kontakte werden geladen ...",
"copied-token-to-clipboard": "Token wurde in die Zwischenablage kopiert", "copied-link-to-clipboard": "Link wurde in die Zwischenablage kopiert",
"count_organizations": "Organisationen (Anzahl)", "copied-token-to-clipboard": "Token wurde in die Zwischenablage kopiert",
"count_teams": "Teams (Anzahl)", "count_organizations": "Organisationen (Anzahl)",
"create": "Erstellen", "count_teams": "Teams (Anzahl)",
"create-a-new": "Erstelle eine neue", "create": "Erstellen",
"create-a-new-card": "Neue Läuferkarte erstellen", "create-a-new": "Erstelle eine neue",
"create-a-new-contact": "Kontakt erstellen", "create-a-new-card": "Neue Läuferkarte erstellen",
"create-a-new-distance-donation": "Erstelle ein neues Sponsoring", "create-a-new-contact": "Kontakt erstellen",
"create-a-new-donor": "Neue Sponsor:in erstellen", "create-a-new-distance-donation": "Erstelle ein neues Sponsoring",
"create-a-new-fixed-donation": "Erstelle eine neue Festbetragsspende", "create-a-new-donor": "Neue Sponsor:in erstellen",
"create-a-new-organization": "Neue Organisation anlegen", "create-a-new-fixed-donation": "Erstelle eine neue Festbetragsspende",
"create-a-new-runner": "Neue Läufer:in erstellen", "create-a-new-organization": "Neue Organisation anlegen",
"create-a-new-scan-fixed-only": "Neuen Scan erstellen (nur mit Festdistanz)", "create-a-new-runner": "Neue Läufer:in erstellen",
"create-a-new-scanstation": "Neue Station erstellen", "create-a-new-scan-fixed-only": "Neuen Scan erstellen (nur mit Festdistanz)",
"create-a-new-statsclient": "Neuen Statsclient erstellen", "create-a-new-scanstation": "Neue Station erstellen",
"create-a-new-team": "Erstelle ein neues Team", "create-a-new-statsclient": "Neuen Statsclient erstellen",
"create-a-new-track": "Neuen Track erstellen", "create-a-new-team": "Erstelle ein neues Team",
"create-a-new-user": "Neue Benutzer:in anlegen", "create-a-new-track": "Neuen Track erstellen",
"create-a-new-user-group": "Erstelle eine neue Gruppe", "create-a-new-user": "Neue Benutzer:in anlegen",
"create-and-generate-pdf": "Erstellen und PDF herunterladen", "create-a-new-user-group": "Erstelle eine neue Gruppe",
"create-bulk-blanco-cards": "Blankokarten erstellen", "create-and-generate-pdf": "Erstellen und PDF herunterladen",
"create-bulk-cards": "Blankokarten erstellen", "create-bulk-blanco-cards": "Blankokarten erstellen",
"create-organization": "Organisation erstellen", "create-bulk-cards": "Blankokarten erstellen",
"create-team": "Team erstellen", "create-organization": "Organisation erstellen",
"create-track": "Track erstellen", "create-team": "Team erstellen",
"create-user": "Benutzer anlegen", "create-track": "Track erstellen",
"create-without-pdf": "Ohne PDF erstellen", "create-user": "Benutzer anlegen",
"created-blanco-cards": "Blankokarten wurden erstellt", "create-without-pdf": "Ohne PDF erstellen",
"creating-blanco-cards": "Erstelle Blankokarten", "created-blanco-cards": "Blankokarten wurden erstellt",
"credits": "Credits", "creating-blanco-cards": "Erstelle Blankokarten",
"csv_import__class": "Klasse", "credits": "Credits",
"csv_import__firstname": "Vorname", "csv_import__class": "Klasse",
"csv_import__lastname": "Nachname", "csv_import__firstname": "Vorname",
"csv_import__middlename": "Mittelname", "csv_import__lastname": "Nachname",
"csv_import__team": "Team", "csv_import__middlename": "Mittelname",
"danger-zone": "Gefahrenzone", "csv_import__team": "Team",
"dashboard-greeting": "Hallo", "danger-zone": "Gefahrenzone",
"dashboard-title": "Dashboard", "dashboard-greeting": "Hallo",
"datatable": { "dashboard-title": "Dashboard",
"search": "🔍 Suche ...", "datatable": {
"an_error_happened_while_fetching_the_data": "Beim Abrufen der Daten ist ein Fehler aufgetreten", "search": "🔍 Suche ...",
"loading": "Wird geladen...", "an_error_happened_while_fetching_the_data": "Beim Abrufen der Daten ist ein Fehler aufgetreten",
"next": "Nächste", "loading": "Wird geladen...",
"of": "von", "next": "Nächste",
"previous": "Vorherige", "of": "von",
"to": "bis", "previous": "Vorherige",
"showing": "Zeige", "to": "bis",
"no_matching_records_found": "Keine passenden Einträge gefunden", "showing": "Zeige",
"page": "Seite", "no_matching_records_found": "Keine passenden Einträge gefunden",
"records": "Einträge", "page": "Seite",
"sort_column_ascending": "Spalte aufsteigend sortieren", "records": "Einträge",
"sort_column_descending": "Spalte absteigend sortieren" "sort_column_ascending": "Spalte aufsteigend sortieren",
}, "sort_column_descending": "Spalte absteigend sortieren"
"delete": "Löschen", },
"delete-contact": "Kontakt löschen", "delete": "Löschen",
"delete-donation": "Sponsoring löschen", "delete-contact": "Kontakt löschen",
"delete-donor": "Sponsor:in löschen", "delete-donation": "Sponsoring löschen",
"delete-group": "Gruppe löschen", "delete-donor": "Sponsor:in löschen",
"delete-organization": "Organisation löschen", "delete-group": "Gruppe löschen",
"delete-profile": "Profil löschen", "delete-organization": "Organisation löschen",
"delete-runner": "Läufer:in löschen", "delete-profile": "Profil löschen",
"delete-scan": "Scan löschen", "delete-runner": "Läufer:in löschen",
"delete-station": "Station löschen", "delete-scan": "Scan löschen",
"delete-statsclient": "Statsclient löschen", "delete-station": "Station löschen",
"delete-team": "Team Löschen", "delete-statsclient": "Statsclient löschen",
"delete-user": "Benutzer:in löschen", "delete-team": "Team Löschen",
"deleted-scan": "Scan wurde gelöscht", "delete-user": "Benutzer:in löschen",
"dependency_name": "Name", "deleted-scan": "Scan wurde gelöscht",
"description": "Beschreibung", "dependency_name": "Name",
"description-optional": "Beschreibung (optional)", "description": "Beschreibung",
"deselect-all": "Alle abwählen", "description-optional": "Beschreibung (optional)",
"details": "Details", "deselect-all": "Alle abwählen",
"disabled": "deaktiviert", "details": "Details",
"distance": "Distanz", "disabled": "deaktiviert",
"distance-donation": "Sponsoring", "distance": "Distanz",
"distance-in-km": "Distanz (in KM)", "distance-donation": "Sponsoring",
"distance-track": "Distanz (+Track)", "distance-in-km": "Distanz (in KM)",
"do-you-really-want-to-delete-your-profile": "Möchtest du dein Profil wirklich löschen?", "distance-track": "Distanz (+Track)",
"do-you-want-to-delete-the-organization-delete_org-name": "Möchtest du die Organisation {orgname} löschen?", "do-you-really-want-to-delete-your-profile": "Möchtest du dein Profil wirklich löschen?",
"do-you-want-to-delete-the-team-delete_team-name": "Möchtest du das Team {teamname} löschen?", "do-you-want-to-delete-the-organization-delete_org-name": "Möchtest du die Organisation {orgname} löschen?",
"do-you-want-to-delete-this-donor-with-all-related-donations": "Möchtest du diese Sponsor:in mit all ihren Sponsorings löschen?", "do-you-want-to-delete-the-team-delete_team-name": "Möchtest du das Team {teamname} löschen?",
"documentation": "Dokumentation", "do-you-want-to-delete-this-donor-with-all-related-donations": "Möchtest du diese Sponsor:in mit all ihren Sponsorings löschen?",
"donation-amount": "Sponsoringbetrag", "documentation": "Dokumentation",
"donation-amount-must-be-greater-that-0-00eur": "Der Sponsoringbetrag muss größer als 0.00€ sein.", "donation-amount": "Sponsoringbetrag",
"donation-deleted": "Sponsoring gelöscht", "donation-amount-must-be-greater-that-0-00eur": "Der Sponsoringbetrag muss größer als 0.00€ sein.",
"donation-updated": "Sponsoring wurde aktualisiert", "donation-deleted": "Sponsoring gelöscht",
"donation_added": "Sponsoring hinzugefügt", "donation-updated": "Sponsoring wurde aktualisiert",
"donations": "Sponsorings", "donation_added": "Sponsoring hinzugefügt",
"donor": "Sponsor:in", "donations": "Sponsorings",
"donor-added": "Sponsor:in hinzugefügt", "donor": "Sponsor:in",
"donor-deleted": "Sponsor:in gelöscht", "donor-added": "Sponsor:in hinzugefügt",
"donor-has-no-associated-donations": "Zur Sponsor:in gibt es noch keine Sponsorings", "donor-deleted": "Sponsor:in gelöscht",
"donor-is-being-added": "Sponsor:in wird hinzugefügt...", "donor-has-no-associated-donations": "Zur Sponsor:in gibt es noch keine Sponsorings",
"donor-is-being-updated": "Sponsor:in wird aktualisiert", "donor-is-being-added": "Sponsor:in wird hinzugefügt...",
"donors": "Sponsor:innen", "donor-is-being-updated": "Sponsor:in wird aktualisiert",
"donors-are-being-loaded": "Sponsor:innen werden geladen", "donors": "Sponsor:innen",
"dont-have-your-email-connected": "Deine E-Mail ist nicht verknüpft?", "donors-are-being-loaded": "Sponsor:innen werden geladen",
"dont-panic-were-resetting-it": "Keine Panik, wir setzen es zurück ✌", "dont-have-your-email-connected": "Deine E-Mail ist nicht verknüpft?",
"e-mail-adress": "E-Mail-Adresse", "dont-panic-were-resetting-it": "Keine Panik, wir setzen es zurück ✌",
"edit": "Bearbeiten", "e-mail-adress": "E-Mail-Adresse",
"edit-a-card": "Läuferkarte bearbeiten", "edit": "Bearbeiten",
"edit-permissions": "Berechtigungen bearbeiten", "edit-a-card": "Läuferkarte bearbeiten",
"email_address_or_username": "E-Mail-Adresse/ Benutzername", "edit-permissions": "Berechtigungen bearbeiten",
"enabled": "aktiviert", "email_address_or_username": "E-Mail-Adresse/ Benutzername",
"enabled_large": "Aktiviert", "enabled": "aktiviert",
"english": "Englisch", "enabled_large": "Aktiviert",
"enter-payment": "Zahlung eingeben", "english": "Englisch",
"error-during-import": "Fehler beim Importieren", "enter-payment": "Zahlung eingeben",
"error-whyile-copying-to-clipboard": "Fehler beim Kopieren in die Zwischenablage", "error-during-import": "Fehler beim Importieren",
"error_on_login": "😢Fehler beim Login", "error-whyile-copying-to-clipboard": "Fehler beim Kopieren in die Zwischenablage",
"erteilte": "Direkt erteilte", "error_on_login": "😢Fehler beim Login",
"everything-concerning-your-profile": "Alles zu deinem Profil", "erteilte": "Direkt erteilte",
"everything-is-more-fun-together": "Im Team macht's mehr Spaß 🏃‍♂️🏃‍♀️🏃‍♂️", "everything-concerning-your-profile": "Alles zu deinem Profil",
"faq": "FAQ", "everything-is-more-fun-together": "Im Team macht's mehr Spaß 🏃‍♂️🏃‍♀️🏃‍♂️",
"filename_sponsoringquittungsliste": "SponsoringQuittungsListe", "faq": "FAQ",
"filter-by-organization-team": "Filtern nach Organisation / Team", "filename_sponsoringquittungsliste": "SponsoringQuittungsListe",
"first-name": "Vorname", "filter-by-organization-team": "Filtern nach Organisation / Team",
"first-name-is-required": "Vorname muss angegeben werden", "first-name": "Vorname",
"first-scan-of-the-day": "Erster Scan des Tages", "first-name-is-required": "Vorname muss angegeben werden",
"fixed-donation": "Festbetragsspende", "first-scan-of-the-day": "Erster Scan des Tages",
"forgot_password": "Passwort vergessen?", "fixed-donation": "Festbetragsspende",
"geerbte": "geerbte", "forgot_password": "Passwort vergessen?",
"general-stats": "Allgemeine Statistiken", "geerbte": "geerbte",
"general_promise_error": "😢 Ein unbekannter Fehler ist aufgetreten", "general-stats": "Allgemeine Statistiken",
"generate-runner-certificate": "Urkunde generieren", "general_promise_error": "😢 Ein unbekannter Fehler ist aufgetreten",
"generate-runner-certificates": "Urkunden generieren", "generate-runner-certificate": "Urkunde generieren",
"generate-runnercards": "Läuferkarten generieren", "generate-runner-certificates": "Urkunden generieren",
"generate-sponsoring-contract": "Sponsoringvertrag generieren", "generate-runnercards": "Läuferkarten generieren",
"generate-sponsoring-contracts": "Sponsoringverträge generieren", "generate-sponsoring-contract": "Sponsoringvertrag generieren",
"generating-pdf": "PDF wird generiert...", "generate-sponsoring-contracts": "Sponsoringverträge generieren",
"generating-pdfs": "PDFs werden generiert...", "generating-pdf": "PDF wird generiert...",
"generic-ui-logic-error": "Etwas ist in der Benutzeroberfläche schiefgelaufen.", "generating-pdfs": "PDFs werden generiert...",
"german": "Deutsch", "generic-ui-logic-error": "Etwas ist in der Benutzeroberfläche schiefgelaufen.",
"go-to-login": "Zum Login", "german": "Deutsch",
"goback": "Zur Startseite", "go-to-login": "Zum Login",
"granted": "Gewährt", "goback": "Zur Startseite",
"group": "Gruppe", "granted": "Gewährt",
"group-added": "Gruppe hinzugefügt", "group": "Gruppe",
"group-is-being-added": "Gruppe wird erstellt", "group-added": "Gruppe hinzugefügt",
"group-name-is-required": "Der Gruppenname muss angegeben werden.", "group-is-being-added": "Gruppe wird erstellt",
"group-updated": "Gruppe aktualisiert", "group-name-is-required": "Der Gruppenname muss angegeben werden.",
"groups": "Gruppen", "group-updated": "Gruppe aktualisiert",
"groups-are-being-loaded": "Gruppen werden geladen", "groups": "Gruppen",
"home": "Start", "groups-are-being-loaded": "Gruppen werden geladen",
"icon-image-credits": "Wir möchten uns außerdem für die verwendeten Icons und Bilder bedanken bei:", "home": "Start",
"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.", "icon-image-credits": "Wir möchten uns außerdem für die verwendeten Icons und Bilder bedanken bei:",
"import-finished": "Import abgeschlossen", "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.",
"import-runners": "Läufer:innen importieren", "import-finished": "Import abgeschlossen",
"import__target-organization": "Ziel Organisation", "import-runners": "Läufer:innen importieren",
"imprint": "Impressum ", "import__target-organization": "Ziel Organisation",
"imprint-loading": "Impressum lädt...", "imprint": "Impressum ",
"inactive": "Inaktiv", "imprint-loading": "Impressum lädt...",
"installed-version": "Installierte Version", "inactive": "Inaktiv",
"internal-error": "Interner Fehler", "installed-version": "Installierte Version",
"invalid": "Ungültig", "internal-error": "Interner Fehler",
"invalid-mail-reset": "Das ist keine gültige E-Mail", "invalid": "Ungültig",
"just-enter-how-many-you-want-and-the-system-will-create-them": "Gebe einfach ein, wie viele Blankokarten das System erstellen soll.", "invalid-mail-reset": "Das ist keine gültige E-Mail",
"key": "Schlüssel", "just-enter-how-many-you-want-and-the-system-will-create-them": "Gebe einfach ein, wie viele Blankokarten das System erstellen soll.",
"laeufer-hinzufuegen": "Läufer:in hinzufügen", "key": "Schlüssel",
"laeufer-importieren": "Läufer:innen importieren", "laeufer-hinzufuegen": "Läufer:in hinzufügen",
"laptime": "Rundenzeit", "laeufer-importieren": "Läufer:innen importieren",
"last-name": "Nachname", "laptime": "Rundenzeit",
"last-name-is-required": "Nachname muss angegeben werden", "last-name": "Nachname",
"lfk-is-os": "Das \"Lauf für Kaya!\" Frontend ist (wie alle anderen Projekte für den \"LfK!\" auch) ein OpenSource Projekt.", "last-name-is-required": "Nachname muss angegeben werden",
"license": "Lizenz", "lfk-is-os": "Das \"Lauf für Kaya!\" Frontend ist (wie alle anderen Projekte für den \"LfK!\" auch) ein OpenSource Projekt.",
"licenses-are-being-loaded": "Lizenzen werden geladen...", "license": "Lizenz",
"loading-cards": "Läuferkarten werden geladen", "licenses-are-being-loaded": "Lizenzen werden geladen...",
"loading-contact-details": "Kontaktdaten werden geladen ...", "loading-cards": "Läuferkarten werden geladen",
"loading-donation-details": "Lade Sponsoringdetails", "loading-contact-details": "Kontaktdaten werden geladen ...",
"loading-donor-details": "Lade Details", "loading-donation-details": "Lade Sponsoringdetails",
"loading-group-detail": "Lade Gruppendetails...", "loading-donor-details": "Lade Details",
"loading-profile-data": "Lade Profildaten", "loading-group-detail": "Lade Gruppendetails...",
"loading-runners": "Läufer:innen werden geladen...", "loading-profile-data": "Lade Profildaten",
"loading-station-details": "Lade Scanstation-Details ...", "loading-runners": "Läufer:innen werden geladen...",
"log_in": "Anmelden", "loading-station-details": "Lade Scanstation-Details ...",
"log_in_to_your_account": "Bitte melde dich an", "log_in": "Anmelden",
"login_is_checked": "Login wird überprüft", "log_in_to_your_account": "Bitte melde dich an",
"logout": "Abmelden", "login_is_checked": "Login wird überprüft",
"mail-validation-in-progress": "E-Mail Verifizierung läuft... ", "logout": "Abmelden",
"manage-admin-users": "Nutzer verwalten", "mail-validation-in-progress": "E-Mail Verifizierung läuft... ",
"middle-name": "Mittelname", "manage-admin-users": "Nutzer verwalten",
"minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)", "middle-name": "Mittelname",
"minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein", "minimum-lap-time-in-s": "Minimale Rundenzeit (in Sekunden)",
"must-be-at-least-10-characters-long": "Passwort muss mindestens 10 Zeichen lang sein!", "minimum-lap-time-must-be-a-positive-number-or-0": "Die minimale Rundenzeit muss eine positive Zahl oder 0 sein",
"must-contain-a-lowercase-letter": "Passwort muss einen Großbuchstaben enthalten!", "must-be-at-least-10-characters-long": "Passwort muss mindestens 10 Zeichen lang sein!",
"must-contain-a-number": "Passwort muss eine Zahl enthalten!", "must-contain-a-lowercase-letter": "Passwort muss einen Großbuchstaben enthalten!",
"must-contain-a-uppercase-letter": "Passwort muss einen Kleinbuchstaben enthalten!", "must-contain-a-number": "Passwort muss eine Zahl enthalten!",
"name": "Name", "must-contain-a-uppercase-letter": "Passwort muss einen Kleinbuchstaben enthalten!",
"name-is-required": "Der Gruppenname muss angegeben werden", "name": "Name",
"new-password": "Neues Passwort", "name-is-required": "Der Gruppenname muss angegeben werden",
"no-contact-found": "Keine Kontakte gefunden", "new-password": "Neues Passwort",
"no-contact-selected": "Kein Kontakt ausgewählt", "no-contact-found": "Keine Kontakte gefunden",
"no-contact-specified": "Kein Kontakt angegeben", "no-contact-selected": "Kein Kontakt ausgewählt",
"no-donors-found": "Keine Spender:innen gefunden", "no-contact-specified": "Kein Kontakt angegeben",
"no-license-text-could-be-found": "Kein Lizenz-Text gefunden 😢", "no-donors-found": "Keine Spender:innen gefunden",
"no-organization-or-team-found": "Keine Organisationen oder Teams gefunden", "no-license-text-could-be-found": "Kein Lizenz-Text gefunden 😢",
"no-organization-specified": "Keine Organisation angegeben", "no-organization-or-team-found": "Keine Organisationen oder Teams gefunden",
"no-organizations-found": "Keine Organisationen gefunden", "no-organization-specified": "Keine Organisation angegeben",
"no-runners-found": "Keine Läufer:innen gefunden", "no-organizations-found": "Keine Organisationen gefunden",
"no-tracks-added-yet": "Es wurden noch keine Tracks erstellt.", "no-runners-found": "Keine Läufer:innen gefunden",
"non-blanko": "Keine/Blankokarte", "no-tracks-added-yet": "Es wurden noch keine Tracks erstellt.",
"open": "OFFEN", "non-blanko": "Keine/Blankokarte",
"organization": "Organisation", "open": "OFFEN",
"organization-added": "Organisation hinzugefügt", "organization": "Organisation",
"organization-deleted": "Organisation gelöscht", "organization-added": "Organisation hinzugefügt",
"organization-detail-is-being-loaded": "Organisationsdetails werden geladen ...", "organization-deleted": "Organisation gelöscht",
"organization-is-being-added": "Organisation wird hinzugefügt ...", "organization-detail-is-being-loaded": "Organisationsdetails werden geladen ...",
"organization-name-is-required": "Der Name muss angegeben werden", "organization-is-being-added": "Organisation wird hinzugefügt ...",
"organizations": "Organisationen", "organization-name-is-required": "Der Name muss angegeben werden",
"organizations-are-being-loaded": "Organisationen werden geladen ...", "organizations": "Organisationen",
"orgs": "Organisationen", "organizations-are-being-loaded": "Organisationen werden geladen ...",
"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!", "orgs": "Organisationen",
"paid": "BEZAHLT", "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!",
"paid-amount": "Gezahlter Betrag", "paid": "BEZAHLT",
"password": "Passwort", "paid-amount": "Gezahlter Betrag",
"password-changed": "Passwort wurde aktualisiert!", "password": "Passwort",
"password-is-required": "Passwort muss angegeben werden", "password-changed": "Passwort wurde aktualisiert!",
"password-reset-failed": "Passwort zurücksetzen ist fehlgeschlagen!", "password-is-required": "Passwort muss angegeben werden",
"password-reset-in-progress": "Passwort wird zurückgesetzt...", "password-reset-failed": "Passwort zurücksetzen ist fehlgeschlagen!",
"password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.", "password-reset-in-progress": "Passwort wird zurückgesetzt...",
"password-reset-successful": "Passwort erfolgreich zurückgesetzt!", "password-reset-mail-sent": "Passwort-Reset Mail wurde an \"{usersEmail}\" geschickt.",
"passwords-dont-match": "Die Passwörter stimmen nicht überein!", "password-reset-successful": "Passwort erfolgreich zurückgesetzt!",
"payment-amount-must-be-greater-than-0-00eur": "Der Zahlungsbetrag muss größer als 0.00€ sein!", "passwords-dont-match": "Die Passwörter stimmen nicht überein!",
"pdf-generation-failed": "PDF Generierung fehlgeschlagen!", "payment-amount-must-be-greater-than-0-00eur": "Der Zahlungsbetrag muss größer als 0.00€ sein!",
"pdf-successfully-generated": "PDF wurde erfolgreich generiert!", "pdf-generation-failed": "PDF Generierung fehlgeschlagen!",
"pdfs-successfully-generated": "Alle PDFs wurden generiert!", "pdf-successfully-generated": "PDF wurde erfolgreich generiert!",
"per-kilometer": "pro Kilometer", "pdfs-successfully-generated": "Alle PDFs wurden generiert!",
"permissions": "Berechtigungen", "per-kilometer": "pro Kilometer",
"permissions-updated": "Berechtigungen aktualisiert!", "permissions": "Berechtigungen",
"phone": "Telefon", "permissions-updated": "Berechtigungen aktualisiert!",
"please-copy-the-token-and-store-it-somewhere-save": "Bitte kopiere dir den Token und bewahre ihn gut auf.", "phone": "Telefon",
"please-provide-a-password": "Bitte gebe ein Passwort an...", "please-copy-the-token-and-store-it-somewhere-save": "Bitte kopiere dir den Token und bewahre ihn gut auf.",
"please-provide-the-nessecary-information-to-add-a-new-donor": "Bitte mach die Notwendigen Angaben, um eine neue Sponsor:in zu erstellen", "please-provide-a-password": "Bitte gebe ein Passwort an...",
"please-provide-the-nessecary-information-to-create-a-new-donation": "Bitte gebe alle für das Sponsoring notwendigen Daten an.", "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-nessecary-information-to-create-a-new-scan": "Bitte gebe alle notwendigen Informationen an, um einen neuen Scan zu erstellen.", "please-provide-the-nessecary-information-to-create-a-new-donation": "Bitte gebe alle für das Sponsoring notwendigen Daten an.",
"please-provide-the-required-csv-xlsx-file": "Bitte eine CSV oder XLSX Datei hochladen.", "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-for-creating-a-new-user-group": "Bitte gebe alle für eine neue Gruppe notwendigen Informationen an.", "please-provide-the-required-csv-xlsx-file": "Bitte eine CSV oder XLSX Datei hochladen.",
"please-provide-the-required-information-to-add-a-new-contact": "Bitte gebe alle nötigen Informationen an, im den neuen Kontakt zu erstellen.", "please-provide-the-required-information-for-creating-a-new-user-group": "Bitte gebe alle für eine neue Gruppe notwendigen Informationen an.",
"please-provide-the-required-information-to-add-a-new-organization": "Bitte gebe alle nötigen Informationen an, im die neue Organisation zu erstellen.", "please-provide-the-required-information-to-add-a-new-contact": "Bitte gebe alle nötigen Informationen an, im den neuen Kontakt zu erstellen.",
"please-provide-the-required-information-to-add-a-new-runner": "Bitte die benötigten Informationen angeben.", "please-provide-the-required-information-to-add-a-new-organization": "Bitte gebe alle nötigen Informationen an, im die neue Organisation zu erstellen.",
"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-required-information-to-add-a-new-runner": "Bitte die benötigten Informationen angeben.",
"please-provide-the-required-information-to-add-a-new-track": "Bitte die benötigten Informationen angeben.", "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-required-information-to-add-a-new-user": "Bitte gebe alle nötigen Informationen an, im die neue Benutzer:in zu erstellen.", "please-provide-the-required-information-to-add-a-new-track": "Bitte die benötigten Informationen angeben.",
"please-provide-the-required-information-to-create-a-new-scanstation": "Bitte gebe alle für eine Scannerstation notwendigen Informationen 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-required-information-to-create-a-new-statsclient": "Bitte gebe alle für einen Statsclient notwendigen Informationen an", "please-provide-the-required-information-to-create-a-new-scanstation": "Bitte gebe alle für eine Scannerstation notwendigen Informationen an",
"please-request-a-new-reset-mail": "Bitte eine neue Passwortreset-Mail anfordern...", "please-provide-the-required-information-to-create-a-new-statsclient": "Bitte gebe alle für einen Statsclient notwendigen Informationen an",
"please-wait-a-moment-your-login-is-still-being-processed": "Bitte warte einen Moment, deine Anmeldung wird verarbeitet", "please-request-a-new-reset-mail": "Bitte eine neue Passwortreset-Mail anfordern...",
"prefix": "Prefix", "please-wait-a-moment-your-login-is-still-being-processed": "Bitte warte einen Moment, deine Anmeldung wird verarbeitet",
"privacy": "Datenschutz", "prefix": "Prefix",
"privacy-loading": "Datenschutzerklärung lädt...", "privacy": "Datenschutz",
"profile": "Profil", "privacy-loading": "Datenschutzerklärung lädt...",
"profile-deleted": "Profil gelöscht!", "profile": "Profil",
"profile-picture": "Profilbild", "profile-deleted": "Profil gelöscht!",
"profile-updated": "Profil wurde aktualisiert!", "profile-picture": "Profilbild",
"read-license": "Lizenz-Text lesen", "profile-updated": "Profil wurde aktualisiert!",
"receipt-needed": "Spendenquittung benötigt", "read-license": "Lizenz-Text lesen",
"repo_link": "Link", "receipt-needed": "Spendenquittung benötigt",
"request-a-new-reset-mail": "Neue Reset-Mail anfordern", "repo_link": "Link",
"reset-my-password": "Passwort zurücksetzen", "request-a-new-reset-mail": "Neue Reset-Mail anfordern",
"reset-password": "Passwort zurücksetzen", "reset-my-password": "Passwort zurücksetzen",
"runner": "Läufer:in", "reset-password": "Passwort zurücksetzen",
"runner-added": "Läufer:in hinzugefügt", "runner": "Läufer:in",
"runner-import": "Läufer:innen Import", "runner-added": "Läufer:in hinzugefügt",
"runner-is-being-added": "Läufer:in wird hinzugefügt...", "runner-import": "Läufer:innen Import",
"runner-updated": "Läufer:in aktualisiert!", "runner-is-being-added": "Läufer:in wird hinzugefügt...",
"runnercards": "Laeuferkarten", "runner-updated": "Läufer:in aktualisiert!",
"runnerimport_verify_runners_org": "Bitte die Läufer:innen für den Import in die Organisation \"{org_name}\" bestätigen", "runnercards": "Laeuferkarten",
"runners": "Läufer", "runnerimport_verify_runners_org": "Bitte die Läufer:innen für den Import in die Organisation \"{org_name}\" bestätigen",
"runners-are-being-imported": "Läufer:innen werden importiert ...", "runners": "Läufer",
"runners-are-being-loaded": "Läufer:innen werden geladen ...", "runners-are-being-imported": "Läufer:innen werden importiert ...",
"save": "Speichern", "runners-are-being-loaded": "Läufer:innen werden geladen ...",
"save-changes": "Änderungen speichern", "save": "Speichern",
"scan-added": "Scan hinzugefügt", "save-changes": "Änderungen speichern",
"scan-is-being-updated": "Scan wird aktualisiert", "scan-added": "Scan hinzugefügt",
"scan-with-fixed-distance": "Scan mit Festdistanz", "scan-is-being-updated": "Scan wird aktualisiert",
"scans": "Scans", "scan-with-fixed-distance": "Scan mit Festdistanz",
"scans-are-being-loaded": "Scans werden geladen", "scans": "Scans",
"scanstation": "Scanner Station", "scans-are-being-loaded": "Scans werden geladen",
"scanstation-added": "Station wurde erstellt", "scanstation": "Scanner Station",
"scanstation-is-being-added": "Scannerstation wird angelegt...", "scanstation-added": "Station wurde erstellt",
"scanstations": "Scanner Stationen", "scanstation-is-being-added": "Scannerstation wird angelegt...",
"scanstations-are-being-loaded": "Scannerstationen werden geladen...", "scanstations": "Scanner Stationen",
"search-for-an-organization-by-name-or-id": "Suche eine Organisation (via Name oder Id)", "scanstations-are-being-loaded": "Scannerstationen werden geladen...",
"search-for-an-organization-or-team-by-name-or-id": "Suche eine Organisation oder ein Team (via Name oder Id)", "search-for-an-organization-by-name-or-id": "Suche eine Organisation (via Name oder Id)",
"search-for-donor-name-or-id": "Suche eine Spender:in (via Name oder Id)", "search-for-an-organization-or-team-by-name-or-id": "Suche eine Organisation oder ein Team (via Name oder Id)",
"search-for-permission": "Berechtigungen durchsuchen", "search-for-donor-name-or-id": "Suche eine Spender:in (via Name oder Id)",
"search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)", "search-for-permission": "Berechtigungen durchsuchen",
"select-all": "Alle auswählen", "search-for-runner-by-name-or-id": "Suche eine Läufer:in (via Name oder Id)",
"select-language": "Sprache auswählen", "select-all": "Alle auswählen",
"selfservice-registration": "Selfservice Registrierung", "select-language": "Sprache auswählen",
"send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services", "selfservice-registration": "Selfservice Registrierung",
"set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen", "send-a-mail-to-lfk-odit-services": "Sende eine Mail an lfk@odit.services",
"settings": "Einstellungen", "set-the-user-active-inactive": "Den Benutzer auf (in)aktiv setzen",
"settings-for-your-profile": "Die Einstellungen deines Accounts", "settings": "Einstellungen",
"something-about-the-group": "Infos zur Gruppe", "settings-for-your-profile": "Die Einstellungen deines Accounts",
"sponsoring-quittungs-liste_herunterladen": "Sponsoring-Quittungs-Liste herunterladen", "something-about-the-group": "Infos zur Gruppe",
"sponsorings": "Sponsoringerklaerungen", "sponsoring-quittungs-liste_herunterladen": "Sponsoring-Quittungs-Liste herunterladen",
"stats-are-being-loaded": "Die Statistiken werden geladen...", "sponsorings": "Sponsoringerklaerungen",
"statsclient-deleted": "Statsclient wurde gelöscht", "stats-are-being-loaded": "Die Statistiken werden geladen...",
"statsclient-is-being-added": "Statsclient wird angelegt...", "statsclient-deleted": "Statsclient wurde gelöscht",
"statsclients": "Statsclient (aka Beamershow)", "statsclient-is-being-added": "Statsclient wird angelegt...",
"statsclients-are-being-loaded": "Statsclients werden geladen", "statsclients": "Statsclient (aka Beamershow)",
"status": "Status", "statsclients-are-being-loaded": "Statsclients werden geladen",
"stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können", "status": "Status",
"successful-password-reset": "Passwort erfolgreich zurückgesetzt!", "stuff-that-could-harm-your-profile": "Einstellungen, die deinem Profil nachhaltig schaden können",
"team": "Team", "successful-password-reset": "Passwort erfolgreich zurückgesetzt!",
"team-added": "Team wurde hinzugefügt", "team": "Team",
"team-deleted": "Team gelöscht", "team-added": "Team wurde hinzugefügt",
"team-detail-is-being-loaded": "Team wird geladen...", "team-deleted": "Team gelöscht",
"team-is-being-added": "Team wird erstellt...", "team-detail-is-being-loaded": "Team wird geladen...",
"team-name": "Teamname", "team-is-being-added": "Team wird erstellt...",
"team-name-is-required": "Teamname ist erforderlich", "team-name": "Teamname",
"teams": "Teams", "team-name-is-required": "Teamname ist erforderlich",
"teams-are-being-loaded": "Teams werden geladen ...", "teams": "Teams",
"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...", "teams-are-being-loaded": "Teams werden geladen ...",
"the-scans-distance-must-be-greater-than-0m": "Die Distanz muss größer als 0m sein.", "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...",
"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!", "the-scans-distance-must-be-greater-than-0m": "Die Distanz muss größer als 0m sein.",
"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!", "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!",
"there-are-no-cards-yet": "Es gibt noch keine Läuferkarten.", "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!",
"there-are-no-contacts-added-yet": "Es wurden noch keine Kontakte hinzugefügt.", "there-are-no-cards-yet": "Es gibt noch keine Läuferkarten.",
"there-are-no-donations-yet": "Es gibt noch keine Sponsorings", "there-are-no-contacts-added-yet": "Es wurden noch keine Kontakte hinzugefügt.",
"there-are-no-donors-yet": "Es gibt noch keine Sponsor:innen", "there-are-no-donations-yet": "Es gibt noch keine Sponsorings",
"there-are-no-groups-yet": "Es gibt noch keine Gruppen", "there-are-no-donors-yet": "Es gibt noch keine Sponsor:innen",
"there-are-no-organizations-added-yet": "Es wurden noch keine Organisationen hinzugefügt.", "there-are-no-groups-yet": "Es gibt noch keine Gruppen",
"there-are-no-runners-added-yet": "Es wurden noch keine Läufer:innen hinzugefügt.", "there-are-no-organizations-added-yet": "Es wurden noch keine Organisationen hinzugefügt.",
"there-are-no-scans-yet": "Es gibt noch keine Scans", "there-are-no-runners-added-yet": "Es wurden noch keine Läufer:innen hinzugefügt.",
"there-are-no-teams-added-yet": "Es wurden noch keine Teams hinzugefügt.", "there-are-no-scans-yet": "Es gibt noch keine Scans",
"there-are-no-users-added-yet": "Es wurden noch keine Benutzer hinzugefügt.", "there-are-no-teams-added-yet": "Es wurden noch keine Teams hinzugefügt.",
"this-card-is": "Diese Karte ist", "there-are-no-users-added-yet": "Es wurden noch keine Benutzer hinzugefügt.",
"this-might-take-a-moment": "Das könnte einen kleinen Moment dauern", "this-card-is": "Diese Karte ist",
"this-scanstation-is": "Diese Station ist", "this-might-take-a-moment": "Das könnte einen kleinen Moment dauern",
"token": "Token", "this-scanstation-is": "Diese Station ist",
"total-distance": "gelaufene Strecke", "token": "Token",
"total-donation-amount": "Gesamtbetrag", "total-distance": "gelaufene Strecke",
"total-donations": "Spendensumme", "total-donation-amount": "Gesamtbetrag",
"total-paid-amount": "Gezahlter Gesamtbetrag", "total-donations": "Spendensumme",
"total-scans": "gesamte Scans", "total-paid-amount": "Gezahlter Gesamtbetrag",
"total_donation_amount_in_eur": "Gesamtbetrag in €", "total-scans": "gesamte Scans",
"track": "Track", "total_donation_amount_in_eur": "Gesamtbetrag in €",
"track-added": "Track hinzugefügt", "track": "Track",
"track-data-is-being-loaded": "Trackdaten werden geladen", "track-added": "Track hinzugefügt",
"track-is-being-added": "Track wird hinzugefügt...", "track-data-is-being-loaded": "Trackdaten werden geladen",
"track-is-being-updated": "Track wird aktualisiert...", "track-is-being-added": "Track wird hinzugefügt...",
"track-length-in-m": "Tracklänge (in Metern)", "track-is-being-updated": "Track wird aktualisiert...",
"track-length-must-be-greater-than-0": "Die Länge muss größer als 0 (Meter) sein", "track-length-in-m": "Tracklänge (in Metern)",
"track-name": "Trackname", "track-length-must-be-greater-than-0": "Die Länge muss größer als 0 (Meter) sein",
"track-name-must-not-be-empty": "Der Name muss angegeben werden", "track-name": "Trackname",
"track-was-updated": "Track wurde aktualisiert", "track-name-must-not-be-empty": "Der Name muss angegeben werden",
"tracks": "Tracks", "track-was-updated": "Track wurde aktualisiert",
"unpaid": "Offen", "tracks": "Tracks",
"update-card": "Karte aktualisieren", "unpaid": "Offen",
"update-password": "Passwort ändern", "update-card": "Karte aktualisieren",
"updated-contact": "Kontakt aktualisiert!", "update-password": "Passwort ändern",
"updated-donor": "Sponsor:in wurde aktualisiert", "updated-contact": "Kontakt aktualisiert!",
"updated-organization": "Organisation wurde aktualisiert", "updated-donor": "Sponsor:in wurde aktualisiert",
"updated-scan": "Scan wurde aktualisiert", "updated-organization": "Organisation wurde aktualisiert",
"updated-team": "Team wurde aktualisiert", "updated-scan": "Scan wurde aktualisiert",
"updateing-group": "Gruppe wird aktualisiert...", "updated-team": "Team wurde aktualisiert",
"updating-card": "Karte wird aktualisiert", "updateing-group": "Gruppe wird aktualisiert...",
"updating-donation": "Sponsoring wird aktualisiert", "updating-card": "Karte wird aktualisiert",
"updating-organization": "Organisation wird aktualisiert", "updating-donation": "Sponsoring wird aktualisiert",
"updating-permissions": "Berechtigungen werden aktualisiert...", "updating-organization": "Organisation wird aktualisiert",
"updating-runner": "Läufer:in wird aktualisiert.", "updating-permissions": "Berechtigungen werden aktualisiert...",
"updating-team": "Team wird aktualisiert", "updating-runner": "Läufer:in wird aktualisiert.",
"updating-user": "Benutzer:in wird aktualisiert...", "updating-team": "Team wird aktualisiert",
"updating-your-profile": "Profil wird aktualisiert...", "updating-user": "Benutzer:in wird aktualisiert...",
"user-added": "Benutzer hinzugefügt", "updating-your-profile": "Profil wird aktualisiert...",
"user-groups": "Benutzergruppen", "user-added": "Benutzer hinzugefügt",
"user-is-being-added": "Benutzer wird hinzugefügt ...", "user-groups": "Benutzergruppen",
"user-updated": "Benutzer:in wurde aktualisiert", "user-is-being-added": "Benutzer wird hinzugefügt ...",
"username": "Benutzername", "user-updated": "Benutzer:in wurde aktualisiert",
"users": "Benutzer", "username": "Benutzername",
"valid": "Gültig", "users": "Benutzer",
"valid-city-is-required": "Du musst eine Stadt angeben", "valid": "Gültig",
"valid-email-is-required": "Es wird eine valide E-Mail Adresse benötigt", "valid-city-is-required": "Du musst eine Stadt angeben",
"valid-international-phone-number-is-required": "Du musst eine Telefonnummer im internationalen Format angeben...", "valid-email-is-required": "Es wird eine valide E-Mail Adresse benötigt",
"valid-zipcode-postal-code-is-required": "Du musst eine valide Postleitzahl angeben", "valid-international-phone-number-is-required": "Du musst eine Telefonnummer im internationalen Format angeben...",
"verfuegbare": "Verfügbar", "valid-zipcode-postal-code-is-required": "Du musst eine valide Postleitzahl angeben",
"welcome_wavinghand": "Willkommen 👋", "verfuegbare": "Verfügbar",
"yes-i-copied-the-token": "Ja, ich habe den Token kopiert", "welcome_wavinghand": "Willkommen 👋",
"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!", "yes-i-copied-the-token": "Ja, ich habe den Token kopiert",
"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-are-going-to-loose-all-permissions-and-access-to-the-runner-system": "Du wirst all deine Berechtigungen und den Zugriff aufs Läufersystem verlieren!",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "Du kannst dich jetzt mit deinem neuen Passwort anmelden! 🎉", "you-can-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-provide-a-runner-but-you-dont-have-to": "Du kannst eine Läufer:in angeben, musst aber nicht.", "you-can-now-use-your-new-password-to-log-in-to-your-account": "Du kannst dich jetzt mit deinem neuen Passwort anmelden! 🎉",
"you-dont-have-any-scanclients-yet": "Es gibt noch keine Statsclients", "you-can-provide-a-runner-but-you-dont-have-to": "Du kannst eine Läufer:in angeben, musst aber nicht.",
"you-dont-have-any-scanstations-yet": "Es gibt noch keine Scannerstationen", "you-dont-have-any-scanclients-yet": "Es gibt noch keine Statsclients",
"you-have-to-provide-an-organization": "Du musst eine Organisation angeben", "you-dont-have-any-scanstations-yet": "Es gibt noch keine Scannerstationen",
"you-have-to-save-your-changes-to-generate-a-link": "Du musst deine Änderungen speichern, um einen Link zu generieren.", "you-have-to-provide-an-organization": "Du musst eine Organisation angeben",
"you-must-create-at-least-one-card-or-cancel": "Du musst mindestens eine Blankokarte erstellen (oder abbrechen).", "you-have-to-save-your-changes-to-generate-a-link": "Du musst deine Änderungen speichern, um einen Link zu generieren.",
"zip-postal-code": "Postleitzahl" "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,476 +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-the-first-statsclient": "Add your first statsclient.", "add-the-first-statsclient": "Add your first statsclient.",
"add-user-group": "Add User Group", "add-user-group": "Add User Group",
"add-your-first-card": "Add your first card", "add-your-first-card": "Add your first card",
"add-your-first-contact": "Add your first contact", "add-your-first-contact": "Add your first contact",
"add-your-first-donor": "add your first donor", "add-your-first-donor": "add your first donor",
"add-your-first-group": "Add your first group", "add-your-first-group": "Add your first group",
"add-your-first-organization": "Add your first organization", "add-your-first-organization": "Add your first organization",
"add-your-first-runner": "Add your first runner", "add-your-first-runner": "Add your first runner",
"add-your-first-team": "Add your first team", "add-your-first-team": "Add your first team",
"add-your-first-track": "Add your first track.", "add-your-first-track": "Add your first track.",
"add-your-first-user": "Add your first user", "add-your-first-user": "Add your first user",
"add-your-fist-donation": "Add your fist donation", "add-your-fist-donation": "Add your fist donation",
"add-your-fist-scan": "Add your fist scan", "add-your-fist-scan": "Add your fist scan",
"adding-card": "Adding Card", "adding-card": "Adding Card",
"adding-donation": "Adding donation...", "adding-donation": "Adding donation...",
"adding-scan": "Adding Scan", "adding-scan": "Adding Scan",
"address": "Address", "address": "Address",
"address-is-required": "Address is required", "address-is-required": "Address is required",
"after-deletion-we-cant-restore-your-old-profile": "After deletion we can't restore your old profile!", "after-deletion-we-cant-restore-your-old-profile": "After deletion we can't restore your old profile!",
"after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "After the update you'll get logged out - Please login with your new password after that.", "after-the-update-youll-get-logged-out-please-login-with-your-new-password-after-that": "After the update you'll get logged out - Please login with your new password after that.",
"all-associated-donations-will-get-deleted-as-well": "All associated donations will get deleted as well", "all": "all",
"all-associated-runners-will-be-deleted-too": "All associated runners will be deleted too!", "all-associated-donations-will-get-deleted-as-well": "All associated donations will get deleted as well",
"all-associated-teams-and-runners-will-be-deleted-too": "All associated teams and runners will be deleted too!", "all-associated-runners-will-be-deleted-too": "All associated runners will be deleted too!",
"already-paid": "Already paid", "all-associated-teams-and-runners-will-be-deleted-too": "All associated teams and runners will be deleted too!",
"amount": "Amount", "already-paid": "Already paid",
"amount-per-kilometer": "Amount per kilometer", "amount": "Amount",
"apartment-suite-etc": "Apartment, suite, etc.", "amount-per-kilometer": "Amount per kilometer",
"application_name": "Lauf für Kaya! - Admin", "apartment-suite-etc": "Apartment, suite, etc.",
"applying-changes": "Applying Changes", "application_name": "Lauf für Kaya! - Admin",
"attention": "Attention!", "applying-changes": "Applying Changes",
"author": "Author", "attention": "Attention!",
"bitte-bestaetige-diese-laeufer-fuer-den-import": "Please confirm these runners for import.", "author": "Author",
"by": "by", "bitte-bestaetige-diese-laeufer-fuer-den-import": "Please confirm these runners for import.",
"cancel": "Cancel", "by": "by",
"cancel-delete": "Cancel Delete", "cancel": "Cancel",
"cancel-keep-donor": "Cancel, keep donor", "cancel-delete": "Cancel Delete",
"cancel-keep-my-profile": "Cancel, keep my profile", "cancel-keep-donor": "Cancel, keep donor",
"cancel-keep-organization": "Cancel, keep organization", "cancel-keep-my-profile": "Cancel, keep my profile",
"cancel-keep-statsclient": "Cancel and keep statsclient", "cancel-keep-organization": "Cancel, keep organization",
"cancel-keep-team": "Cancel, keep team", "cancel-keep-statsclient": "Cancel and keep statsclient",
"cannot-reset-your-password-directly": "Bummer. We unfortunately cannot reset your password directly. Please send us a mail and confirm your identity", "cancel-keep-team": "Cancel, keep team",
"card-added": "Card added", "cannot-reset-your-password-directly": "Bummer. We unfortunately cannot reset your password directly. Please send us a mail and confirm your identity",
"card-deleted": "Card deleted", "card-added": "Card added",
"card-updated": "Card updated", "card-deleted": "Card deleted",
"cards": "Cards", "card-updated": "Card updated",
"certificates": "Certificates", "cards": "Cards",
"change-your-password-here": "Change your password here", "certificates": "Certificates",
"changing-your-password": "Changing your password", "change-your-password-here": "Change your password here",
"city": "City", "changing-your-password": "Changing your password",
"click-to-copy-the-link-into-your-clipboard": "Click to copy the link into your clipboard", "city": "City",
"click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard", "click-to-copy-the-link-into-your-clipboard": "Click to copy the link into your clipboard",
"close": "Close", "click-to-copy-token-to-clipboard": "Click to copy the token to your clipboard",
"code": "Code", "close": "Close",
"configure-the-tracks-and-minimum-lap-times": "configure the tracks & minimum lap times", "code": "Code",
"confirm": "Confirm", "configure-the-tracks-and-minimum-lap-times": "configure the tracks & minimum lap times",
"confirm-delete": "Confirm Delete", "confirm": "Confirm",
"confirm-delete-donor-with-all-donations": "Confirm, delete donor with all donations", "confirm-delete": "Confirm Delete",
"confirm-delete-my-user-profile": "Confirm, delete my user profile", "confirm-delete-donor-with-all-donations": "Confirm, delete donor with all donations",
"confirm-delete-organization-and-associated-teams-runners": "Confirm, delete organization and associated teams+runners.", "confirm-delete-my-user-profile": "Confirm, delete my user profile",
"confirm-delete-statsclient": "Confirm, delete statsclient", "confirm-delete-organization-and-associated-teams-runners": "Confirm, delete organization and associated teams+runners.",
"confirm-delete-team-and-associated-runners": "Confirm, delete team and associated runners.", "confirm-delete-statsclient": "Confirm, delete statsclient",
"confirm-deletion": "Confirm Deletion", "confirm-delete-team-and-associated-runners": "Confirm, delete team and associated runners.",
"confirm-the-new-password": "Confirm the new password", "confirm-deletion": "Confirm Deletion",
"contact": "Contact", "confirm-the-new-password": "Confirm the new password",
"contact-added": "Contact added", "contact": "Contact",
"contact-deleted": "Contact deleted", "contact-added": "Contact added",
"contact-information": "Contact Information", "contact-deleted": "Contact deleted",
"contact-is-being-added": "Contact is being added...", "contact-information": "Contact Information",
"contact-is-being-updated": "Contact is being updated...", "contact-is-being-added": "Contact is being added...",
"contact-is-not-a-member-in-any-group": "Contact is not a member in any group", "contact-is-being-updated": "Contact is being updated...",
"contacts": "Contacts", "contact-is-not-a-member-in-any-group": "Contact is not a member in any group",
"contacts-are-being-loaded": "contacts are being loaded...", "contacts": "Contacts",
"copied-link-to-clipboard": "Copied link to clipboard", "contacts-are-being-loaded": "contacts are being loaded...",
"copied-token-to-clipboard": "Copied token to clipboard", "copied-link-to-clipboard": "Copied link to clipboard",
"count_organizations": "# Organizations", "copied-token-to-clipboard": "Copied token to clipboard",
"count_teams": "# Teams", "count_organizations": "# Organizations",
"create": "Create", "count_teams": "# Teams",
"create-a-new": "Create a new", "create": "Create",
"create-a-new-card": "Create a new card", "create-a-new": "Create a new",
"create-a-new-contact": "Create a new contact", "create-a-new-card": "Create a new card",
"create-a-new-distance-donation": "Create a new distance donation", "create-a-new-contact": "Create a new contact",
"create-a-new-donor": "Create a new donor", "create-a-new-distance-donation": "Create a new distance donation",
"create-a-new-fixed-donation": "Create a new fixed donation", "create-a-new-donor": "Create a new donor",
"create-a-new-organization": "Create a new Organization", "create-a-new-fixed-donation": "Create a new fixed donation",
"create-a-new-runner": "Create a new Runner", "create-a-new-organization": "Create a new Organization",
"create-a-new-scan-fixed-only": "Create a new scan (fixed only)", "create-a-new-runner": "Create a new Runner",
"create-a-new-scanstation": "Create a new station", "create-a-new-scan-fixed-only": "Create a new scan (fixed only)",
"create-a-new-statsclient": "Create a new statsclient", "create-a-new-scanstation": "Create a new station",
"create-a-new-team": "Create a new team", "create-a-new-statsclient": "Create a new statsclient",
"create-a-new-track": "Create a new Track", "create-a-new-team": "Create a new team",
"create-a-new-user": "Create a new User", "create-a-new-track": "Create a new Track",
"create-a-new-user-group": "Create a new user group", "create-a-new-user": "Create a new User",
"create-and-generate-pdf": "Create and generate PDF", "create-a-new-user-group": "Create a new user group",
"create-bulk-blanco-cards": "Create bulk blanco cards", "create-and-generate-pdf": "Create and generate PDF",
"create-bulk-cards": "Add blanco cards", "create-bulk-blanco-cards": "Create bulk blanco cards",
"create-organization": "Create Organization", "create-bulk-cards": "Add blanco cards",
"create-team": "Create Team", "create-organization": "Create Organization",
"create-track": "Create Track", "create-team": "Create Team",
"create-user": "Create User", "create-track": "Create Track",
"create-without-pdf": "Create without PDF", "create-user": "Create User",
"created-blanco-cards": "Created blanco cards", "create-without-pdf": "Create without PDF",
"creating-blanco-cards": "Creating blanco cards", "created-blanco-cards": "Created blanco cards",
"credits": "Credits", "creating-blanco-cards": "Creating blanco cards",
"csv_import__class": "Class", "credits": "Credits",
"csv_import__firstname": "Firstname", "csv_import__class": "Class",
"csv_import__lastname": "Lastname", "csv_import__firstname": "Firstname",
"csv_import__middlename": "Middlename", "csv_import__lastname": "Lastname",
"csv_import__team": "Team", "csv_import__middlename": "Middlename",
"danger-zone": "Danger zone", "csv_import__team": "Team",
"dashboard-greeting": "Hello", "danger-zone": "Danger zone",
"dashboard-title": "Dashboard", "dashboard-greeting": "Hello",
"datatable": { "dashboard-title": "Dashboard",
"search": "🔍 Search...", "datatable": {
"sort_column_ascending": "Sort column ascending", "search": "🔍 Search...",
"sort_column_descending": "Sort column descending", "sort_column_ascending": "Sort column ascending",
"previous": "Previous", "sort_column_descending": "Sort column descending",
"next": "Next", "previous": "Previous",
"page": "Page", "next": "Next",
"showing": "Showing", "page": "Page",
"records": "Records", "showing": "Showing",
"of": "of", "records": "Records",
"to": "to", "of": "of",
"loading": "Loading...", "to": "to",
"no_matching_records_found": "No matching records found", "loading": "Loading...",
"an_error_happened_while_fetching_the_data": "An error happened while fetching the data" "no_matching_records_found": "No matching records found",
}, "an_error_happened_while_fetching_the_data": "An error happened while fetching the data"
"delete": "Delete", },
"delete-contact": "Delete Contact", "delete": "Delete",
"delete-donation": "Delete Donation", "delete-contact": "Delete Contact",
"delete-donor": "Delete donor", "delete-donation": "Delete Donation",
"delete-group": "Delete Group", "delete-donor": "Delete donor",
"delete-organization": "Delete Organization", "delete-group": "Delete Group",
"delete-profile": "Delete Profile", "delete-organization": "Delete Organization",
"delete-runner": "Delete Runner", "delete-profile": "Delete Profile",
"delete-scan": "Delete scan", "delete-runner": "Delete Runner",
"delete-station": "Delete station", "delete-scan": "Delete scan",
"delete-statsclient": "Delete statsclient", "delete-station": "Delete station",
"delete-team": "Delete Team", "delete-statsclient": "Delete statsclient",
"delete-user": "Delete User", "delete-team": "Delete Team",
"deleted-scan": "Deleted scan", "delete-user": "Delete User",
"dependency_name": "Name", "deleted-scan": "Deleted scan",
"description": "description", "dependency_name": "Name",
"description-optional": "Description (optional)", "description": "description",
"deselect-all": "deselect all", "description-optional": "Description (optional)",
"details": "Details", "deselect-all": "deselect all",
"disabled": "disabled", "details": "Details",
"distance": "Distance", "disabled": "disabled",
"distance-donation": "distance donation", "distance": "Distance",
"distance-in-km": "Distance in km", "distance-donation": "distance donation",
"distance-track": "Distance (+Track)", "distance-in-km": "Distance in km",
"do-you-really-want-to-delete-your-profile": "Do you really want to delete your profile?", "distance-track": "Distance (+Track)",
"do-you-want-to-delete-the-organization-delete_org-name": "Do you want to delete the organization {orgname}?", "do-you-really-want-to-delete-your-profile": "Do you really want to delete your profile?",
"do-you-want-to-delete-the-team-delete_team-name": "Do you want to delete the team {teamname}?", "do-you-want-to-delete-the-organization-delete_org-name": "Do you want to delete the organization {orgname}?",
"do-you-want-to-delete-this-donor-with-all-related-donations": "Do you want to delete this donor with all related donations", "do-you-want-to-delete-the-team-delete_team-name": "Do you want to delete the team {teamname}?",
"documentation": "Documentation", "do-you-want-to-delete-this-donor-with-all-related-donations": "Do you want to delete this donor with all related donations",
"donation-amount": "Donation amount", "documentation": "Documentation",
"donation-amount-must-be-greater-that-0-00eur": "Donation amount must be greater that 0.00€", "donation-amount": "Donation amount",
"donation-deleted": "Donation deleted", "donation-amount-must-be-greater-that-0-00eur": "Donation amount must be greater that 0.00€",
"donation-updated": "Donation updated", "donation-deleted": "Donation deleted",
"donation_added": "Donation_added", "donation-updated": "Donation updated",
"donations": "Donations", "donation_added": "Donation_added",
"donor": "Donor", "donations": "Donations",
"donor-added": "Donor added", "donor": "Donor",
"donor-deleted": "donor deleted", "donor-added": "Donor added",
"donor-has-no-associated-donations": "Donor has no associated donations.", "donor-deleted": "donor deleted",
"donor-is-being-added": "Donor is being added...", "donor-has-no-associated-donations": "Donor has no associated donations.",
"donor-is-being-updated": "Donor is being updated", "donor-is-being-added": "Donor is being added...",
"donors": "Donors", "donor-is-being-updated": "Donor is being updated",
"donors-are-being-loaded": "donors are being loaded", "donors": "Donors",
"dont-have-your-email-connected": "Don't have your email connected?", "donors-are-being-loaded": "donors are being loaded",
"dont-panic-were-resetting-it": "Don't panic, we're resetting it ✌", "dont-have-your-email-connected": "Don't have your email connected?",
"e-mail-adress": "E-Mail Adress", "dont-panic-were-resetting-it": "Don't panic, we're resetting it ✌",
"edit": "Edit", "e-mail-adress": "E-Mail Adress",
"edit-a-card": "Edit a card", "edit": "Edit",
"edit-permissions": "edit permissions", "edit-a-card": "Edit a card",
"email_address_or_username": "Email / username", "edit-permissions": "edit permissions",
"enabled": "enabled", "email_address_or_username": "Email / username",
"enabled_large": "Enabled", "enabled": "enabled",
"english": "English", "enabled_large": "Enabled",
"enter-payment": "Enter payment", "english": "English",
"error-during-import": "Error during import", "enter-payment": "Enter payment",
"error-whyile-copying-to-clipboard": "Error while copying to clipboard", "error-during-import": "Error during import",
"error_on_login": "Error on login", "error-whyile-copying-to-clipboard": "Error while copying to clipboard",
"erteilte": "Directly granted", "error_on_login": "Error on login",
"everything-concerning-your-profile": "Everything concerning your profile", "erteilte": "Directly granted",
"everything-is-more-fun-together": "everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️", "everything-concerning-your-profile": "Everything concerning your profile",
"faq": "FAQ", "everything-is-more-fun-together": "everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️",
"filename_sponsoringquittungsliste": "DonorReceiptList", "faq": "FAQ",
"filter-by-organization-team": "Filter by Organization/ Team", "filename_sponsoringquittungsliste": "DonorReceiptList",
"first-name": "First name", "filter-by-organization-team": "Filter by Organization/ Team",
"first-name-is-required": "First Name is required", "first-name": "First name",
"first-scan-of-the-day": "First scan of the day.", "first-name-is-required": "First Name is required",
"fixed-donation": "fixed donation", "first-scan-of-the-day": "First scan of the day.",
"forgot_password": "Forgot your password?", "fixed-donation": "fixed donation",
"geerbte": "inherited", "forgot_password": "Forgot your password?",
"general-stats": "General Stats", "geerbte": "inherited",
"general_promise_error": "😢 Error", "general-stats": "General Stats",
"generate-runner-certificate": "Generate runner certificate", "general_promise_error": "😢 Error",
"generate-runner-certificates": "Generate runner certificates", "generate-runner-certificate": "Generate runner certificate",
"generate-runnercards": "Generate Runnercards", "generate-runner-certificates": "Generate runner certificates",
"generate-sponsoring-contract": "generate sponsoring contract", "generate-runnercards": "Generate Runnercards",
"generate-sponsoring-contracts": "generate sponsoring contracts", "generate-sponsoring-contract": "generate sponsoring contract",
"generating-pdf": "generating PDF...", "generate-sponsoring-contracts": "generate sponsoring contracts",
"generating-pdfs": "generating PDFs...", "generating-pdf": "generating PDF...",
"generic-ui-logic-error": "Something went wrong in the UI logic", "generating-pdfs": "generating PDFs...",
"german": "German", "generic-ui-logic-error": "Something went wrong in the UI logic",
"go-to-login": "Go To Login", "german": "German",
"goback": "Go Home", "go-to-login": "Go To Login",
"granted": "granted", "goback": "Go Home",
"group": "Group", "granted": "granted",
"group-added": "Group added", "group": "Group",
"group-is-being-added": "Group is being added...", "group-added": "Group added",
"group-name-is-required": "Group name is required", "group-is-being-added": "Group is being added...",
"group-updated": "group updated", "group-name-is-required": "Group name is required",
"groups": "Groups", "group-updated": "group updated",
"groups-are-being-loaded": "Groups are being loaded", "groups": "Groups",
"home": "Home", "groups-are-being-loaded": "Groups are being loaded",
"icon-image-credits": "We also want to thank these projects for illustrations and icons:", "home": "Home",
"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.", "icon-image-credits": "We also want to thank these projects for illustrations and icons:",
"import-finished": "Import finished", "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.",
"import-runners": "Import runners", "import-finished": "Import finished",
"import__target-organization": "Target Organization", "import-runners": "Import runners",
"imprint": "Imprint", "import__target-organization": "Target Organization",
"imprint-loading": "Imprint loading...", "imprint": "Imprint",
"inactive": "Inactive", "imprint-loading": "Imprint loading...",
"installed-version": "Installed version", "inactive": "Inactive",
"internal-error": "Internal Error", "installed-version": "Installed version",
"invalid": "Invalid", "internal-error": "Internal Error",
"invalid-mail-reset": "the provided email is invalid", "invalid": "Invalid",
"just-enter-how-many-you-want-and-the-system-will-create-them": "Just enter how many you want and the system will create them", "invalid-mail-reset": "the provided email is invalid",
"key": "Key", "just-enter-how-many-you-want-and-the-system-will-create-them": "Just enter how many you want and the system will create them",
"laeufer-hinzufuegen": "Add runner", "key": "Key",
"laeufer-importieren": "Läufer importieren", "laeufer-hinzufuegen": "Add runner",
"laptime": "Laptime", "laeufer-importieren": "Läufer importieren",
"last-name": "Last name", "laptime": "Laptime",
"last-name-is-required": "Last Name is required", "last-name": "Last name",
"lfk-is-os": "The \"Lauf für Kaya!\" Frontend is (like all other projects for the \"LfK!\" Also) an open source project.", "last-name-is-required": "Last Name is required",
"license": "License", "lfk-is-os": "The \"Lauf für Kaya!\" Frontend is (like all other projects for the \"LfK!\" Also) an open source project.",
"licenses-are-being-loaded": "Licenses are being loaded...", "license": "License",
"loading-cards": "Loading cards", "licenses-are-being-loaded": "Licenses are being loaded...",
"loading-contact-details": "Loading contact details...", "loading-cards": "Loading cards",
"loading-donation-details": "Loading donation details", "loading-contact-details": "Loading contact details...",
"loading-donor-details": "Loading donor details", "loading-donation-details": "Loading donation details",
"loading-group-detail": "Loading group detail...", "loading-donor-details": "Loading donor details",
"loading-profile-data": "Loading profile data", "loading-group-detail": "Loading group detail...",
"loading-runners": "loading runners...", "loading-profile-data": "Loading profile data",
"loading-station-details": "Loading station details", "loading-runners": "loading runners...",
"log_in": "Log in", "loading-station-details": "Loading station details",
"log_in_to_your_account": "Log in to your account", "log_in": "Log in",
"login_is_checked": "Login is being checked...", "log_in_to_your_account": "Log in to your account",
"logout": "Logout", "login_is_checked": "Login is being checked...",
"mail-validation-in-progress": "mail validation in progress...", "logout": "Logout",
"manage-admin-users": "manage admin users", "mail-validation-in-progress": "mail validation in progress...",
"middle-name": "Middle name", "manage-admin-users": "manage admin users",
"minimum-lap-time-in-s": "minimum lap time in s", "middle-name": "Middle name",
"minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0", "minimum-lap-time-in-s": "minimum lap time in s",
"must-be-at-least-10-characters-long": "Must be at least 10 characters long!", "minimum-lap-time-must-be-a-positive-number-or-0": "minimum lap time must be a positive number or 0",
"must-contain-a-lowercase-letter": "Must contain a lowercase letter!", "must-be-at-least-10-characters-long": "Must be at least 10 characters long!",
"must-contain-a-number": "Must contain a number!", "must-contain-a-lowercase-letter": "Must contain a lowercase letter!",
"must-contain-a-uppercase-letter": "Must contain a uppercase letter!", "must-contain-a-number": "Must contain a number!",
"name": "Name", "must-contain-a-uppercase-letter": "Must contain a uppercase letter!",
"name-is-required": "Name is required", "name": "Name",
"new-password": "New password", "name-is-required": "Name is required",
"no-contact-found": "No contacts found", "new-password": "New password",
"no-contact-selected": "No contact selected", "no-contact-found": "No contacts found",
"no-contact-specified": "no contact specified", "no-contact-selected": "No contact selected",
"no-donors-found": "No donors found", "no-contact-specified": "no contact specified",
"no-license-text-could-be-found": "No license text could be found 😢", "no-donors-found": "No donors found",
"no-organization-or-team-found": "No organization or team found", "no-license-text-could-be-found": "No license text could be found 😢",
"no-organization-specified": "no organization specified", "no-organization-or-team-found": "No organization or team found",
"no-organizations-found": "No organizations found", "no-organization-specified": "no organization specified",
"no-runners-found": "No runners found", "no-organizations-found": "No organizations found",
"no-tracks-added-yet": "there are no tracks added yet.", "no-runners-found": "No runners found",
"non-blanko": "Non/Blanko", "no-tracks-added-yet": "there are no tracks added yet.",
"open": "OPEN", "non-blanko": "Non/Blanko",
"organization": "Organization", "open": "OPEN",
"organization-added": "Organization added", "organization": "Organization",
"organization-deleted": "Organization deleted", "organization-added": "Organization added",
"organization-detail-is-being-loaded": "organization detail is being loaded...", "organization-deleted": "Organization deleted",
"organization-is-being-added": "Organization is being added...", "organization-detail-is-being-loaded": "organization detail is being loaded...",
"organization-name-is-required": "Organization name is required", "organization-is-being-added": "Organization is being added...",
"organizations": "Organizations", "organization-name-is-required": "Organization name is required",
"organizations-are-being-loaded": "organizations are being loaded...", "organizations": "Organizations",
"orgs": "Organizations", "organizations-are-being-loaded": "organizations are being loaded...",
"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!", "orgs": "Organizations",
"paid": "PAID", "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!",
"paid-amount": "Paid amount", "paid": "PAID",
"password": "Password", "paid-amount": "Paid amount",
"password-changed": "Password changed!", "password": "Password",
"password-is-required": "Password is required", "password-changed": "Password changed!",
"password-reset-failed": "Password reset failed!", "password-is-required": "Password is required",
"password-reset-in-progress": "Password Reset in Progress...", "password-reset-failed": "Password reset failed!",
"password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".", "password-reset-in-progress": "Password Reset in Progress...",
"password-reset-successful": "Password Reset successful!", "password-reset-mail-sent": "Password reset mail was sent to \"{usersEmail}\".",
"passwords-dont-match": "Passwords don't match!", "password-reset-successful": "Password Reset successful!",
"payment-amount-must-be-greater-than-0-00eur": "Payment amount must be greater than 0.00€!", "passwords-dont-match": "Passwords don't match!",
"pdf-generation-failed": "PDF generation failed!", "payment-amount-must-be-greater-than-0-00eur": "Payment amount must be greater than 0.00€!",
"pdf-successfully-generated": "PDF successfully generated!", "pdf-generation-failed": "PDF generation failed!",
"pdfs-successfully-generated": "PDFs successfully generated!", "pdf-successfully-generated": "PDF successfully generated!",
"per-kilometer": "per Kilometer", "pdfs-successfully-generated": "PDFs successfully generated!",
"permissions": "Permissions", "per-kilometer": "per Kilometer",
"permissions-updated": "Permissions updated!", "permissions": "Permissions",
"phone": "Phone", "permissions-updated": "Permissions updated!",
"please-copy-the-token-and-store-it-somewhere-save": "Please copy the token and store it somewhere safe.", "phone": "Phone",
"please-provide-a-password": "Please provide a password...", "please-copy-the-token-and-store-it-somewhere-save": "Please copy the token and store it somewhere safe.",
"please-provide-the-nessecary-information-to-add-a-new-donor": "Please provide the nessecary information to add a new donor", "please-provide-a-password": "Please provide a password...",
"please-provide-the-nessecary-information-to-create-a-new-donation": "Please provide the nessecary information to create a new donation", "please-provide-the-nessecary-information-to-add-a-new-donor": "Please provide the nessecary information to add a new donor",
"please-provide-the-nessecary-information-to-create-a-new-scan": "Please provide the nessecary information to create a new scan.", "please-provide-the-nessecary-information-to-create-a-new-donation": "Please provide the nessecary information to create a new donation",
"please-provide-the-required-csv-xlsx-file": "Please provide the required csv/ xlsx file", "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-for-creating-a-new-user-group": "Please provide the required information for creating a new user group.", "please-provide-the-required-csv-xlsx-file": "Please provide the required csv/ xlsx file",
"please-provide-the-required-information-to-add-a-new-contact": "Please provide the required information to add a new contact.", "please-provide-the-required-information-for-creating-a-new-user-group": "Please provide the required information for creating a new user group.",
"please-provide-the-required-information-to-add-a-new-organization": "Please provide the required information to add a new organization.", "please-provide-the-required-information-to-add-a-new-contact": "Please provide the required information to add a new contact.",
"please-provide-the-required-information-to-add-a-new-runner": "Please provide the required information to add a new runner.", "please-provide-the-required-information-to-add-a-new-organization": "Please provide the required information to add a new organization.",
"please-provide-the-required-information-to-add-a-new-team": "Please provide the required information to add a new team.", "please-provide-the-required-information-to-add-a-new-runner": "Please provide the required information to add a new runner.",
"please-provide-the-required-information-to-add-a-new-track": "Please provide the required information to add a new track.", "please-provide-the-required-information-to-add-a-new-team": "Please provide the required information to add a new team.",
"please-provide-the-required-information-to-add-a-new-user": "Please provide the required information to add a new user.", "please-provide-the-required-information-to-add-a-new-track": "Please provide the required information to add a new track.",
"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-to-add-a-new-user": "Please provide the required information to add a new user.",
"please-provide-the-required-information-to-create-a-new-statsclient": "Please provide the required information to create a new statsclient", "please-provide-the-required-information-to-create-a-new-scanstation": "Please provide the required information to create a new scanstation",
"please-request-a-new-reset-mail": "Please request a new reset mail...", "please-provide-the-required-information-to-create-a-new-statsclient": "Please provide the required information to create a new statsclient",
"please-wait-a-moment-your-login-is-still-being-processed": "Please wait a moment, your login is still being processed", "please-request-a-new-reset-mail": "Please request a new reset mail...",
"prefix": "Prefix", "please-wait-a-moment-your-login-is-still-being-processed": "Please wait a moment, your login is still being processed",
"privacy": "Privacy", "prefix": "Prefix",
"privacy-loading": "Privacy loading...", "privacy": "Privacy",
"profile": "Profile", "privacy-loading": "Privacy loading...",
"profile-deleted": "Profile deleted!", "profile": "Profile",
"profile-picture": "Profile Picture", "profile-deleted": "Profile deleted!",
"profile-updated": "Profile updated!", "profile-picture": "Profile Picture",
"read-license": "Read License", "profile-updated": "Profile updated!",
"receipt-needed": "Receipt needed", "read-license": "Read License",
"repo_link": "Link", "receipt-needed": "Receipt needed",
"request-a-new-reset-mail": "Request a new reset mail", "repo_link": "Link",
"reset-my-password": "Reset my password", "request-a-new-reset-mail": "Request a new reset mail",
"reset-password": "Reset your password", "reset-my-password": "Reset my password",
"runner": "Runner", "reset-password": "Reset your password",
"runner-added": "Runner added", "runner": "Runner",
"runner-import": "Runner Import", "runner-added": "Runner added",
"runner-is-being-added": "Runner is being added...", "runner-import": "Runner Import",
"runner-updated": "Runner updated!", "runner-is-being-added": "Runner is being added...",
"runnercards": "Runnercards", "runner-updated": "Runner updated!",
"runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"", "runnercards": "Runnercards",
"runners": "Runners", "runnerimport_verify_runners_org": "Please confirm these runners for import into the organization \"{org_name}\"",
"runners-are-being-imported": "Runners are being imported...", "runners": "Runners",
"runners-are-being-loaded": "runners are being loaded...", "runners-are-being-imported": "Runners are being imported...",
"save": "Save", "runners-are-being-loaded": "runners are being loaded...",
"save-changes": "Save Changes", "save": "Save",
"scan-added": "Scan added", "save-changes": "Save Changes",
"scan-is-being-updated": "Scan is being updated", "scan-added": "Scan added",
"scan-with-fixed-distance": "Scan with fixed distance", "scan-is-being-updated": "Scan is being updated",
"scans": "Scans", "scan-with-fixed-distance": "Scan with fixed distance",
"scans-are-being-loaded": "Scans are being loaded", "scans": "Scans",
"scanstation": "Scanstation", "scans-are-being-loaded": "Scans are being loaded",
"scanstation-added": "Scanstation added", "scanstation": "Scanstation",
"scanstation-is-being-added": "Adding scanstation...", "scanstation-added": "Scanstation added",
"scanstations": "Scanstations", "scanstation-is-being-added": "Adding scanstation...",
"scanstations-are-being-loaded": "Loading scanstations...", "scanstations": "Scanstations",
"search-for-an-organization-by-name-or-id": "Search for an organization (by name or id)", "scanstations-are-being-loaded": "Loading scanstations...",
"search-for-an-organization-or-team-by-name-or-id": "Search for an organization or team (by name or id)", "search-for-an-organization-by-name-or-id": "Search for an organization (by name or id)",
"search-for-donor-name-or-id": "Search for donor (by name or id)", "search-for-an-organization-or-team-by-name-or-id": "Search for an organization or team (by name or id)",
"search-for-permission": "Search for permission", "search-for-donor-name-or-id": "Search for donor (by name or id)",
"search-for-runner-by-name-or-id": "Search for runner (by name or id)", "search-for-permission": "Search for permission",
"select-all": "select all", "search-for-runner-by-name-or-id": "Search for runner (by name or id)",
"select-language": "Select language", "select-all": "select all",
"selfservice-registration": "Selfservice registration", "select-language": "Select language",
"send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services", "selfservice-registration": "Selfservice registration",
"set-the-user-active-inactive": "set the user active/ inactive", "send-a-mail-to-lfk-odit-services": "send a mail to lfk@odit.services",
"settings": "Settings", "set-the-user-active-inactive": "set the user active/ inactive",
"settings-for-your-profile": "Settings for your profile", "settings": "Settings",
"something-about-the-group": "Something about the group...", "settings-for-your-profile": "Settings for your profile",
"sponsoring-quittungs-liste_herunterladen": "Download donor receipt list", "something-about-the-group": "Something about the group...",
"sponsorings": "Sponsorings", "sponsoring-quittungs-liste_herunterladen": "Download donor receipt list",
"stats-are-being-loaded": "stats are being loaded...", "sponsorings": "Sponsorings",
"statsclient-deleted": "Deleted statsclient", "stats-are-being-loaded": "stats are being loaded...",
"statsclient-is-being-added": "Statsclient is being added...", "statsclient-deleted": "Deleted statsclient",
"statsclients": "Statsclients (aka Beamershow)", "statsclient-is-being-added": "Statsclient is being added...",
"statsclients-are-being-loaded": "Loading statsclients", "statsclients": "Statsclients (aka Beamershow)",
"status": "Status", "statsclients-are-being-loaded": "Loading statsclients",
"stuff-that-could-harm-your-profile": "Stuff that could harm your profile", "status": "Status",
"successful-password-reset": "Successful password reset!", "stuff-that-could-harm-your-profile": "Stuff that could harm your profile",
"team": "Team", "successful-password-reset": "Successful password reset!",
"team-added": "Team added", "team": "Team",
"team-deleted": "Team deleted", "team-added": "Team added",
"team-detail-is-being-loaded": "team detail is being loaded...", "team-deleted": "Team deleted",
"team-is-being-added": "Team is being added...", "team-detail-is-being-loaded": "team detail is being loaded...",
"team-name": "Team name", "team-is-being-added": "Team is being added...",
"team-name-is-required": "team name is required", "team-name": "Team name",
"teams": "Teams", "team-name-is-required": "team name is required",
"teams-are-being-loaded": "teams are being loaded...", "teams": "Teams",
"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...", "teams-are-being-loaded": "teams are being loaded...",
"the-scans-distance-must-be-greater-than-0m": "The scan's distance must be greater than 0m", "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...",
"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!", "the-scans-distance-must-be-greater-than-0m": "The scan's distance must be greater than 0m",
"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!", "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!",
"there-are-no-cards-yet": "There are no cards yet.", "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!",
"there-are-no-contacts-added-yet": "There are no contacts added yet.", "there-are-no-cards-yet": "There are no cards yet.",
"there-are-no-donations-yet": "There are no donations yet", "there-are-no-contacts-added-yet": "There are no contacts added yet.",
"there-are-no-donors-yet": "There are no donors yet", "there-are-no-donations-yet": "There are no donations yet",
"there-are-no-groups-yet": "There are no groups yet", "there-are-no-donors-yet": "There are no donors yet",
"there-are-no-organizations-added-yet": "There are no organizations added yet.", "there-are-no-groups-yet": "There are no groups yet",
"there-are-no-runners-added-yet": "There are no runners added yet.", "there-are-no-organizations-added-yet": "There are no organizations added yet.",
"there-are-no-scans-yet": "There are no scans yet", "there-are-no-runners-added-yet": "There are no runners added yet.",
"there-are-no-teams-added-yet": "There are no teams added yet.", "there-are-no-scans-yet": "There are no scans yet",
"there-are-no-users-added-yet": "There are no users added yet.", "there-are-no-teams-added-yet": "There are no teams added yet.",
"this-card-is": "This card is", "there-are-no-users-added-yet": "There are no users added yet.",
"this-might-take-a-moment": "This might take a moment 👀", "this-card-is": "This card is",
"this-scanstation-is": "This scanstation is", "this-might-take-a-moment": "This might take a moment 👀",
"token": "Token", "this-scanstation-is": "This scanstation is",
"total-distance": "total distance", "token": "Token",
"total-donation-amount": "total donation amount", "total-distance": "total distance",
"total-donations": "total donations", "total-donation-amount": "total donation amount",
"total-paid-amount": "Total paid amount", "total-donations": "total donations",
"total-scans": "total scans", "total-paid-amount": "Total paid amount",
"total_donation_amount_in_eur": "Total donation amount in €", "total-scans": "total scans",
"track": "Track", "total_donation_amount_in_eur": "Total donation amount in €",
"track-added": "Track added", "track": "Track",
"track-data-is-being-loaded": "Track data is being loaded", "track-added": "Track added",
"track-is-being-added": "Track is being added...", "track-data-is-being-loaded": "Track data is being loaded",
"track-is-being-updated": "Track is being updated...", "track-is-being-added": "Track is being added...",
"track-length-in-m": "Track Length in m", "track-is-being-updated": "Track is being updated...",
"track-length-must-be-greater-than-0": "Track length must be greater than 0", "track-length-in-m": "Track Length in m",
"track-name": "Track name", "track-length-must-be-greater-than-0": "Track length must be greater than 0",
"track-name-must-not-be-empty": "Track name must not be empty", "track-name": "Track name",
"track-was-updated": "Track was updated!", "track-name-must-not-be-empty": "Track name must not be empty",
"tracks": "Tracks", "track-was-updated": "Track was updated!",
"unpaid": "Unpaid", "tracks": "Tracks",
"update-card": "Update Card", "unpaid": "Unpaid",
"update-password": "Update password", "update-card": "Update Card",
"updated-contact": "Updated contact!", "update-password": "Update password",
"updated-donor": "updated donor", "updated-contact": "Updated contact!",
"updated-organization": "updated organization", "updated-donor": "updated donor",
"updated-scan": "updated scan", "updated-organization": "updated organization",
"updated-team": "Updated team", "updated-scan": "updated scan",
"updateing-group": "updateing group...", "updated-team": "Updated team",
"updating-card": "Updating card", "updateing-group": "updateing group...",
"updating-donation": "Updating donation", "updating-card": "Updating card",
"updating-organization": "updating organization", "updating-donation": "Updating donation",
"updating-permissions": "updating permissions...", "updating-organization": "updating organization",
"updating-runner": "Updating runner...", "updating-permissions": "updating permissions...",
"updating-team": "Updating team", "updating-runner": "Updating runner...",
"updating-user": "updating user...", "updating-team": "Updating team",
"updating-your-profile": "Updating your profile...", "updating-user": "updating user...",
"user-added": "User added", "updating-your-profile": "Updating your profile...",
"user-groups": "User Groups", "user-added": "User added",
"user-is-being-added": "User is being added...", "user-groups": "User Groups",
"user-updated": "User updated", "user-is-being-added": "User is being added...",
"username": "Username", "user-updated": "User updated",
"users": "Users", "username": "Username",
"valid": "Valid", "users": "Users",
"valid-city-is-required": "Valid city is required", "valid": "Valid",
"valid-email-is-required": "valid email is required", "valid-city-is-required": "Valid city is required",
"valid-international-phone-number-is-required": "valid international phone number is required...", "valid-email-is-required": "valid email is required",
"valid-zipcode-postal-code-is-required": "Valid zipcode/ postal code is required", "valid-international-phone-number-is-required": "valid international phone number is required...",
"verfuegbare": "availdable", "valid-zipcode-postal-code-is-required": "Valid zipcode/ postal code is required",
"welcome_wavinghand": "Welcome 👋", "verfuegbare": "availdable",
"yes-i-copied-the-token": "Yes, I copied the token", "welcome_wavinghand": "Welcome 👋",
"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!", "yes-i-copied-the-token": "Yes, I copied the token",
"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-are-going-to-loose-all-permissions-and-access-to-the-runner-system": "You are going to loose all permissions and access to the runner system!",
"you-can-now-use-your-new-password-to-log-in-to-your-account": "You can now use your new password to log in to your account! 🎉", "you-can-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-provide-a-runner-but-you-dont-have-to": "You can provide a runner, but you don't have to.", "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-dont-have-any-scanclients-yet": "You don't have any statsclients yet", "you-can-provide-a-runner-but-you-dont-have-to": "You can provide a runner, but you don't have to.",
"you-dont-have-any-scanstations-yet": "You don't have any scanstations yet", "you-dont-have-any-scanclients-yet": "You don't have any statsclients yet",
"you-have-to-provide-an-organization": "You have to provide an organization", "you-dont-have-any-scanstations-yet": "You don't have any scanstations yet",
"you-have-to-save-your-changes-to-generate-a-link": "You have to save your changes to generate a link.", "you-have-to-provide-an-organization": "You have to provide an organization",
"you-must-create-at-least-one-card-or-cancel": "You must create at least one card (or cancel).", "you-have-to-save-your-changes-to-generate-a-link": "You have to save your changes to generate a link.",
"zip-postal-code": "ZIP/ postal code" "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()
]
};
});