Compare commits

...

260 Commits

Author SHA1 Message Date
0708cabc75 🚀RELEASE v1.1.0
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 16:05:21 +02:00
4fcb26cf93 feat(dashboard): Updated stats icons 2023-04-19 16:04:02 +02:00
269def20d1 feat(dashboard): Added average sponsoring
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 15:59:45 +02:00
b8de9e0e42 feat(dashboard): Added average distance 2023-04-19 15:57:46 +02:00
7b2b598588 feat(dashboard): Added total donations 2023-04-19 15:56:21 +02:00
e0b61486b0 feat(dashboar): Added total donors to overview 2023-04-19 15:54:39 +02:00
3f86f7412f Lockfile 2023-04-19 15:53:33 +02:00
6454d960de Bumped client 2023-04-19 15:53:28 +02:00
37154c188b Merge branch 'dev' of git.odit.services:lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 15:38:15 +02:00
8da7578a0a 🚀RELEASE v1.0.0 2023-04-19 15:37:56 +02:00
2a64094006 new license file version [CI SKIP] 2023-04-19 13:37:32 +00:00
e9ce9644ff Merge pull request 'feature/175-request_pagination' (#176) from feature/175-request_pagination into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #176
2023-04-19 13:37:16 +00:00
52439aa5bc Allways set loaded to true
ref #175
2023-04-18 20:47:42 +02:00
ccf865687b Donation paginated loading
ref #175
2023-04-18 20:46:18 +02:00
cac34db1fd Paginated scan loading
ref #175
2023-04-18 20:43:04 +02:00
faf3893180 Paginated runner loading (1000 per page)
ref #175
2023-04-18 20:40:28 +02:00
c33dfcfddd Implemented Async loading of cards via pagination (500 cards per request)
ref #175
2023-04-18 20:38:01 +02:00
019e14ab1f Bumped lfk client
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-18 20:18:46 +02:00
b5790196c6 Merge branch 'dev' of git.odit.services:lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-17 17:56:42 +02:00
94a64ca690 🚀RELEASE v0.19.0 2023-04-17 17:56:34 +02:00
a6ce04c903 new license file version [CI SKIP] 2023-04-17 15:56:08 +00:00
165c154233 Merge pull request 'feature/173-scanstation_configcodes' (#174) from feature/173-scanstation_configcodes into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #174
2023-04-17 15:55:53 +00:00
318547db46 Adjusted size on smaller devices
ref #173
2023-04-17 17:55:33 +02:00
e60c09e19c I18n
ref #173
2023-04-17 17:52:05 +02:00
4834d1484c Styling
ref #173
2023-04-17 17:51:46 +02:00
4b6342727e Implemented config code generation
ref #173
2023-04-17 17:47:57 +02:00
cb5fa52cd9 Barcode placeholder
ref #173
2023-04-17 17:32:42 +02:00
947d01cf7f Lockfile 2023-04-17 17:32:31 +02:00
3563394fb3 Added bwip-js for barcode generation
ref #173
2023-04-17 17:32:16 +02:00
269d7a7def 🚀RELEASE v0.18.4
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 23:32:08 +02:00
e95f2333b0 Hide address2 in orgs by default 2023-04-15 23:30:46 +02:00
950217e0a3 🚀RELEASE v0.18.3
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 23:27:36 +02:00
5e65fb3301 Dont show adress 2 in runner overview 2023-04-15 23:27:19 +02:00
2a294cde04 🚀RELEASE v0.18.2
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-15 19:58:14 +02:00
e95420d79c Push in releaseit 2023-04-15 19:57:59 +02:00
cffbd17dc7 Added timestamps to scanoverview 2023-04-15 19:56:55 +02:00
00de8c3d75 🚀RELEASE v0.18.1
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 19:44:17 +02:00
1f4711d07a Merge branch 'dev' of git.odit.services:lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 19:43:12 +02:00
30e3396897 Missing scanstation translations 2023-04-15 19:43:06 +02:00
5291e049a1 fix(ConfirmStatsClientDeletion): ScanStationService -> StatsClientService
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 19:42:19 +02:00
08fbb504c9 fix(ConfirmScanStationDeletion): donorControllerRemove -> scanStationControllerRemove 2023-04-15 19:41:42 +02:00
e9ca1d3e5d Merge branch 'dev' of git.odit.services:lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-12 21:36:33 +02:00
eb80406fdb 🚀RELEASE v0.18.0 2023-04-12 21:36:26 +02:00
9fe53b0b9c fix: button onclick a11y
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-12 21:36:16 +02:00
9ae5e62e5d Module 2023-04-12 21:36:10 +02:00
1613ae7de6 Versionbuilder 2023-04-12 21:35:32 +02:00
ed1caa7be7 new license file version [CI SKIP] 2023-04-12 19:32:09 +00:00
d88f3a5a27 Merge pull request 'experiment/tanstack' (#172) from experiment/tanstack into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #172
2023-04-12 19:31:47 +00:00
c98eb49ae3 drop legacy dependencies 2023-04-12 21:31:25 +02:00
6d9d8a4724 Scans deletion 2023-04-12 21:25:33 +02:00
7d8c68a455 Scan deletion 2023-04-12 21:25:26 +02:00
8f33640bec Reactivated generate cards modal 2023-04-12 21:20:56 +02:00
f89023e24a Always load bulk created cards 2023-04-12 21:17:53 +02:00
f5d14f2e18 Scan reactive add 2023-04-12 21:14:37 +02:00
9811ede3b2 removed unused import 2023-04-12 21:11:56 +02:00
b2e51fea48 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 21:11:08 +02:00
2a915620c9 Import add to datatable 2023-04-12 21:11:06 +02:00
526688935f Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 21:10:51 +02:00
c7dcf7c66d scan delete working 2023-04-12 21:10:36 +02:00
06411dc147 Add runners reactivity 2023-04-12 21:09:40 +02:00
195d182cc9 Add Card appends current cards 2023-04-12 21:04:29 +02:00
17217dae76 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 20:54:33 +02:00
f105cc0a41 wip: delete scans 2023-04-12 20:54:09 +02:00
2f62c7ae89 Fixed killing of the dom 2023-04-12 20:54:00 +02:00
f6c1fea17c Added status filter function 2023-04-12 20:47:36 +02:00
178dc93319 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 20:43:09 +02:00
8ffe8eff06 Basic card delete modal 2023-04-12 20:43:08 +02:00
bd4952ee57 wip:ScanValid badge 2023-04-12 20:40:04 +02:00
4b171fd04f ScansOverview: use CardRunner link 2023-04-12 20:36:56 +02:00
2c198cfde8 Added custom filter to scan overview 2023-04-12 20:33:30 +02:00
7c6d39b5fa Added custom runner filter 2023-04-12 20:32:24 +02:00
9c13b2f9e9 fix(CardsOverview): table scroll + checkbox posititioning 2023-04-12 20:27:12 +02:00
1ec9556aa6 ScansOverview: fallback for manual scans 2023-04-12 20:22:59 +02:00
adec38b50b Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 20:20:31 +02:00
a35af6f020 Moved data loading to onmount 2023-04-12 20:20:30 +02:00
e74ff5e885 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 20:20:10 +02:00
c87561f63b wip: ScansOverview -> new datatable 2023-04-12 20:19:48 +02:00
c681570134 Fixed edit dispatch 2023-04-12 20:15:43 +02:00
53b945c72f Moved update card logic to overview 2023-04-12 20:14:04 +02:00
f6985daec7 Cards details modal 2023-04-12 20:07:46 +02:00
5662c3b6da Basic card table replace 2023-04-12 20:06:26 +02:00
9def0b27c9 fix: formatting 2023-04-12 19:56:29 +02:00
52a02c82d2 yeeted extra table styling 2023-04-12 19:47:48 +02:00
c241961d0a Extracted table header 2023-04-12 19:46:33 +02:00
8f50555a06 Extracted table bottom 2023-04-12 19:44:48 +02:00
b35375c929 fix min-w th 2023-04-12 19:43:15 +02:00
bf1e715261 RunnersOverview: table responsiveness 2023-04-12 19:41:41 +02:00
0240e1dca2 import 2023-04-12 19:36:35 +02:00
7cec2a00c5 Fixed id sorting 2023-04-12 19:34:54 +02:00
239f79fecb Moved code around 2023-04-12 19:28:10 +02:00
0265a59b82 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 19:26:56 +02:00
57dce34fc5 fix: table sort button + search style 2023-04-12 19:26:54 +02:00
d8110580e9 Added middle-name 2023-04-12 19:25:33 +02:00
03b7ada5ef Close modal on delete and import toastify 2023-04-12 19:23:27 +02:00
1df505ea00 fix(DeleteRunnerModal): ESC key 2023-04-12 19:22:52 +02:00
3e8dac3203 Reload table data on delete 2023-04-12 19:22:26 +02:00
95707a71a9 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 19:21:19 +02:00
fc2c2907c4 Disable sort for actions 2023-04-12 19:21:01 +02:00
ebdd1c2c0c quick cleanup 2023-04-12 19:20:03 +02:00
13254b24dd Fixed styling 2023-04-12 19:15:28 +02:00
e17eb64031 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 19:14:44 +02:00
a0727a0291 Extracted deletion into function of overview 2023-04-12 19:14:42 +02:00
f7f7926829 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 19:11:13 +02:00
9b7dca341b Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 19:11:08 +02:00
da3300562a Delete modal logic 2023-04-12 19:11:07 +02:00
e2ddb5a14c Added delete toast 2023-04-12 19:05:57 +02:00
fb9645aed6 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 19:05:02 +02:00
a4ebc7e126 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 19:04:22 +02:00
fd5db7d68a Added delete runner modal 2023-04-12 19:04:15 +02:00
e7eddb4f08 drop redundant button role 2023-04-12 19:03:49 +02:00
94155845f0 RunnersOverview: add filter keyboard support 2023-04-12 19:00:10 +02:00
3abf608b15 improved tablefilters/groupFilter 2023-04-12 18:56:55 +02:00
d31fe2363b ammendme 2023-04-12 18:51:48 +02:00
11a56f87e8 Implemented table buttons 2023-04-12 18:51:42 +02:00
19793cdcd4 Merge branch 'experiment/tanstack' of git.odit.services:lfk/frontend into experiment/tanstack 2023-04-12 18:47:52 +02:00
9363773fa1 Basic details and delete buttons 2023-04-12 18:47:47 +02:00
c7990882cf fix: TracksOverview 2023-04-12 18:46:31 +02:00
d4ab76ea1b RunnersOverview: pass selectedRunners to actions 2023-04-12 18:42:16 +02:00
2c992a0e63 RunnersOverview: disable debug log 2023-04-12 18:37:38 +02:00
88f96acc3c RunnersOverview: TopActionSection: add margin top 2023-04-12 18:37:26 +02:00
245db06173 updated default table row count 2023-04-12 18:37:02 +02:00
49c2cd5c4b show certificate, runnercard, sponsoring contract section 2023-04-12 18:36:49 +02:00
64db553185 fix: checkbox styling 2023-04-12 18:31:43 +02:00
a06a19ce9c Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 18:29:25 +02:00
592ddc1541 Merge branch 'experiment/tanstack' of https://git.odit.services/lfk/frontend into experiment/tanstack 2023-04-12 18:29:15 +02:00
cb5f2b73d0 drop old datatables 2023-04-12 18:29:06 +02:00
2304b12c1c Moved filter function to shared 2023-04-12 18:28:48 +02:00
38d3e1912c Unused filter function value 2023-04-12 18:26:36 +02:00
fbc14fd7b4 Make the linter happy 2023-04-12 16:31:56 +02:00
0283df22c8 Group filters 2023-04-12 16:30:00 +02:00
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
65e8998894 set table-layout:fixed + display when loaded
ref #146
2023-02-16 11:26:37 +01:00
449948050b 🚀RELEASE v0.16.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-15 15:05:33 +01:00
cf97281592 fix: donor detail: sponsorings: unset middlename will show as "null"
All checks were successful
continuous-integration/drone/push Build is passing
close #145
2023-02-15 15:05:01 +01:00
75684efa1a 🚀RELEASE v0.16.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-03 14:07:27 +01:00
2c4f27a943 new license file version [CI SKIP] 2023-02-03 13:05:38 +00:00
53b7dec7cd Merge pull request 'feature/143-beamershow_clients' (#144) from feature/143-beamershow_clients into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #144
2023-02-03 13:05:01 +00:00
e0cbfb000b Tailwind bump 2023-02-02 17:27:18 +01:00
3a66f4c862 new license file version [CI SKIP] 2023-02-02 16:23:56 +00:00
85ceaa464f Merge branch 'dev' into feature/143-beamershow_clients 2023-02-02 17:23:33 +01:00
976755338b Updated docker base images
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-02 17:21:58 +01:00
1c980059cf Switched drone to kaniko 2023-02-02 17:20:01 +01:00
2d8c4c1698 Ignore pnpm lock 2023-02-02 17:19:51 +01:00
19a333d7bd Added missing translation
ref #143
2023-02-02 17:16:24 +01:00
96c55db63d Added translation
ref #143
2023-02-02 17:13:16 +01:00
fecb07ee37 Re-added copy modal
ref #143
2023-02-02 17:11:46 +01:00
e10c6480a5 Removed Key after creation
ref #143
2023-02-02 17:08:29 +01:00
86 changed files with 9111 additions and 3496 deletions

View File

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

View File

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

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,8 +2,339 @@
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.
#### [1.1.0](https://git.odit.services/lfk/frontend/compare/1.0.0...1.1.0)
- feat(dashboar): Added total donors to overview [`e0b6148`](https://git.odit.services/lfk/frontend/commit/e0b61486b089aa1e611ef3569b1521fc331ec0e4)
- feat(dashboard): Updated stats icons [`4fcb26c`](https://git.odit.services/lfk/frontend/commit/4fcb26cf9371e27e5d7e474b3558ef354e9114c0)
- feat(dashboard): Added average sponsoring [`269def2`](https://git.odit.services/lfk/frontend/commit/269def20d114ededaba3153bbd50ec2ddd70e1c6)
- feat(dashboard): Added total donations [`7b2b598`](https://git.odit.services/lfk/frontend/commit/7b2b59858839b98370af6fb1e6028ba0a1639186)
- feat(dashboard): Added average distance [`b8de9e0`](https://git.odit.services/lfk/frontend/commit/b8de9e0e427b3a8b56e6354ad7168ae12c7cce85)
- Lockfile [`3f86f74`](https://git.odit.services/lfk/frontend/commit/3f86f7412ffc4bc27328ad1f7d3c3118546e7e29)
- Bumped client [`6454d96`](https://git.odit.services/lfk/frontend/commit/6454d960de3f9f5ea86679f157b3b7e7cffde74d)
- new license file version [CI SKIP] [`2a64094`](https://git.odit.services/lfk/frontend/commit/2a640940062765a470387103a72ed14a2411d97b)
### [1.0.0](https://git.odit.services/lfk/frontend/compare/0.19.0...1.0.0)
> 19 April 2023
- 🚀RELEASE v1.0.0 [`8da7578`](https://git.odit.services/lfk/frontend/commit/8da7578a0a46a3e97d8c870e29399f6e8821c9fa)
- Merge pull request 'feature/175-request_pagination' (#176) from feature/175-request_pagination into dev [`e9ce964`](https://git.odit.services/lfk/frontend/commit/e9ce9644ff03f981cec6e9ad56aa5fdf0ff71ef4)
- Donation paginated loading [`ccf8656`](https://git.odit.services/lfk/frontend/commit/ccf865687b34016931a702c0a9b98a0a18e2b03a)
- Paginated scan loading [`cac34db`](https://git.odit.services/lfk/frontend/commit/cac34db1fd3bf5dc7c7be64b3a76ca4c8c77938d)
- Implemented Async loading of cards via pagination (500 cards per request) [`c33dfcf`](https://git.odit.services/lfk/frontend/commit/c33dfcfddddfed0902f3fa9b1d8a1d3e1560262f)
- Paginated runner loading (1000 per page) [`faf3893`](https://git.odit.services/lfk/frontend/commit/faf3893180bb735bea6f1ea58c896686b89949fe)
- Allways set loaded to true [`52439aa`](https://git.odit.services/lfk/frontend/commit/52439aa5bc8cfb1d78d5dfce55b1a0df640ad8f5)
- Bumped lfk client [`019e14a`](https://git.odit.services/lfk/frontend/commit/019e14ab1f99906f13d36c7148d0f4b7894072f2)
- new license file version [CI SKIP] [`a6ce04c`](https://git.odit.services/lfk/frontend/commit/a6ce04c90386f16abf235cc7b2f95aeea5011c7d)
#### [0.19.0](https://git.odit.services/lfk/frontend/compare/0.18.4...0.19.0)
> 17 April 2023
- 🚀RELEASE v0.19.0 [`94a64ca`](https://git.odit.services/lfk/frontend/commit/94a64ca69078c7fe2935eeb5f955fab95a79cb85)
- Merge pull request 'feature/173-scanstation_configcodes' (#174) from feature/173-scanstation_configcodes into dev [`165c154`](https://git.odit.services/lfk/frontend/commit/165c1542338c58f2abf42fef2e7b84b40d1e2d9c)
- I18n [`e60c09e`](https://git.odit.services/lfk/frontend/commit/e60c09e19c9cc20338906e84f4db4e009d926360)
- Implemented config code generation [`4b63427`](https://git.odit.services/lfk/frontend/commit/4b6342727ee0ea38597750d8c99edc301f1ccc2d)
- Styling [`4834d14`](https://git.odit.services/lfk/frontend/commit/4834d1484c3fb6ecd4a1b56aa9fbb8125c641a62)
- Adjusted size on smaller devices [`318547d`](https://git.odit.services/lfk/frontend/commit/318547db46045e41de64d5688368e85cd6fb8035)
- Lockfile [`947d01c`](https://git.odit.services/lfk/frontend/commit/947d01cf7fc7fe2ee88c56e017b0d663f1f3b4f9)
- Barcode placeholder [`cb5fa52`](https://git.odit.services/lfk/frontend/commit/cb5fa52cd9a97490b50fb0c02c26615b49650c08)
- Added bwip-js for barcode generation [`3563394`](https://git.odit.services/lfk/frontend/commit/3563394fb33d661890327e2ae08c400830b37844)
#### [0.18.4](https://git.odit.services/lfk/frontend/compare/0.18.3...0.18.4)
> 15 April 2023
- 🚀RELEASE v0.18.4 [`269d7a7`](https://git.odit.services/lfk/frontend/commit/269d7a7defdde059ef2bb5103262cf734e9babe9)
- Hide address2 in orgs by default [`e95f233`](https://git.odit.services/lfk/frontend/commit/e95f2333b0b958ed00c0e097b43aac2e70ad0d38)
#### [0.18.3](https://git.odit.services/lfk/frontend/compare/0.18.2...0.18.3)
> 15 April 2023
- 🚀RELEASE v0.18.3 [`950217e`](https://git.odit.services/lfk/frontend/commit/950217e0a350f9999b879475edf41f2f11c48179)
- Dont show adress 2 in runner overview [`5e65fb3`](https://git.odit.services/lfk/frontend/commit/5e65fb33013c3dad38e7ad6740b017ae206f278f)
#### [0.18.2](https://git.odit.services/lfk/frontend/compare/0.18.1...0.18.2)
> 15 April 2023
- 🚀RELEASE v0.18.2 [`2a294cd`](https://git.odit.services/lfk/frontend/commit/2a294cde040044bbebfb9c8b34b6c91b27772741)
- Added timestamps to scanoverview [`cffbd17`](https://git.odit.services/lfk/frontend/commit/cffbd17dc77054048cc9b14891f960f9a3fd18cb)
- Push in releaseit [`e95420d`](https://git.odit.services/lfk/frontend/commit/e95420d79c3227c0ca0cf0c0b599970c2b7d690e)
#### [0.18.1](https://git.odit.services/lfk/frontend/compare/0.18.0...0.18.1)
> 15 April 2023
- 🚀RELEASE v0.18.1 [`00de8c3`](https://git.odit.services/lfk/frontend/commit/00de8c3d75e90cd4614f42111f5f45bedde64130)
- Missing scanstation translations [`30e3396`](https://git.odit.services/lfk/frontend/commit/30e33968978bf33cedb31bcbf63fac273e1664f5)
- fix: button onclick a11y [`9fe53b0`](https://git.odit.services/lfk/frontend/commit/9fe53b0b9c71e8a6b4aa3f317327ffe729df0834)
- fix(ConfirmStatsClientDeletion): ScanStationService -&gt; StatsClientService [`5291e04`](https://git.odit.services/lfk/frontend/commit/5291e049a1d2e880fbe277095da91b70d4812c3f)
- fix(ConfirmScanStationDeletion): donorControllerRemove -&gt; scanStationControllerRemove [`08fbb50`](https://git.odit.services/lfk/frontend/commit/08fbb504c958415ce75e1e426296f870f0f1358d)
#### [0.18.0](https://git.odit.services/lfk/frontend/compare/0.17.3...0.18.0)
> 12 April 2023
- Moved filter function to typed version [`#171`](https://git.odit.services/lfk/frontend/issues/171)
- ScansOverview: migrate to datatable [`#168`](https://git.odit.services/lfk/frontend/issues/168)
- 🚀RELEASE v0.18.0 [`eb80406`](https://git.odit.services/lfk/frontend/commit/eb80406fdb8abf3f76bca742095e8f1f03480a56)
- wip: ScansOverview -&gt; new datatable [`c87561f`](https://git.odit.services/lfk/frontend/commit/c87561f63b90ab951daf91d9b8b54ba96a94cc7f)
- Basic card table replace [`5662c3b`](https://git.odit.services/lfk/frontend/commit/5662c3b6da67c00c94254bf39f8820e531fc93ef)
- RunnersOverview: table responsiveness [`bf1e715`](https://git.odit.services/lfk/frontend/commit/bf1e715261c0076fd8543dd1187c516209a73b16)
- Basic card delete modal [`8ffe8ef`](https://git.odit.services/lfk/frontend/commit/8ffe8eff0623767809cdc49ea15cf2d30b609285)
- wip: delete scans [`f105cc0`](https://git.odit.services/lfk/frontend/commit/f105cc0a41972261610d03015ecd5ee492dab8e2)
- Added delete runner modal [`fd5db7d`](https://git.odit.services/lfk/frontend/commit/fd5db7d68ab4b32f7bb14bd0275a447b749e1fd8)
- Extracted table bottom [`8f50555`](https://git.odit.services/lfk/frontend/commit/8f50555a06a35d0b11ac1d9201851d00d915c4b8)
- fix: formatting [`9def0b2`](https://git.odit.services/lfk/frontend/commit/9def0b27c9958c5c75466f56c17b6dc4d44d2b50)
- fix(CardsOverview): table scroll + checkbox posititioning [`9c13b2f`](https://git.odit.services/lfk/frontend/commit/9c13b2f9e938ad36cb74c66a8f91a5b872c8c2d1)
- Extracted table header [`c241961`](https://git.odit.services/lfk/frontend/commit/c241961d0ab0d6b8c3bf0453c1a21d84027ed62f)
- drop legacy dependencies [`c98eb49`](https://git.odit.services/lfk/frontend/commit/c98eb49ae31d1d28de79c7a9c945ade50828f07f)
- scan delete working [`c7dcf7c`](https://git.odit.services/lfk/frontend/commit/c7dcf7c66d7141117e08462ad05f6f441565d012)
- Add Card appends current cards [`195d182`](https://git.odit.services/lfk/frontend/commit/195d182cc977b4ac9f342f09a9ea69d461892a95)
- Moved data loading to onmount [`a35af6f`](https://git.odit.services/lfk/frontend/commit/a35af6f02055115d60d040baaf22dabbeda38498)
- Add runners reactivity [`06411dc`](https://git.odit.services/lfk/frontend/commit/06411dc14747fc9803da009a29688789c31dfc42)
- quick cleanup [`ebdd1c2`](https://git.odit.services/lfk/frontend/commit/ebdd1c2c0c2191c60dfacd0c4be989748628ca63)
- Extracted deletion into function of overview [`a0727a0`](https://git.odit.services/lfk/frontend/commit/a0727a02913708de06be8bf1b9f8cbae5489f080)
- Moved update card logic to overview [`53b945c`](https://git.odit.services/lfk/frontend/commit/53b945c72fa5b30f03eff8bb83f8f25d85d79b07)
- wip:ScanValid badge [`bd4952e`](https://git.odit.services/lfk/frontend/commit/bd4952ee575b4cd8caea3608b14d8c51b37a55eb)
- yeeted extra table styling [`52a02c8`](https://git.odit.services/lfk/frontend/commit/52a02c82d2bdabdf3e632910aa415c1ffb97fd7f)
- Added custom runner filter [`7c6d39b`](https://git.odit.services/lfk/frontend/commit/7c6d39b5fa60d1b834c9fb74dd26dfc2225161d6)
- Scan reactive add [`f5d14f2`](https://git.odit.services/lfk/frontend/commit/f5d14f2e1810462cba99b2482320746f0750d4a1)
- Moved code around [`239f79f`](https://git.odit.services/lfk/frontend/commit/239f79fecba732c807f4296cf524be15c1ccff1e)
- Scan deletion [`7d8c68a`](https://git.odit.services/lfk/frontend/commit/7d8c68a4550407e8c871545e7a0c5f622a1e3b9c)
- Added status filter function [`f6c1fea`](https://git.odit.services/lfk/frontend/commit/f6c1fea17ce64d890ac810d9dff058dd589c2989)
- Added delete toast [`e2ddb5a`](https://git.odit.services/lfk/frontend/commit/e2ddb5a14cc8b450c00129709bf5b933a47866dd)
- Fixed killing of the dom [`2f62c7a`](https://git.odit.services/lfk/frontend/commit/2f62c7ae89b7dda3bc0efd4d56e677898a3f197f)
- Cards details modal [`f6985da`](https://git.odit.services/lfk/frontend/commit/f6985daec71e4d922acd8a9b2673fd41317b075f)
- Reload table data on delete [`3e8dac3`](https://git.odit.services/lfk/frontend/commit/3e8dac3203f56723a3dad4a35887d60fc03d4ae3)
- Added middle-name [`d811058`](https://git.odit.services/lfk/frontend/commit/d8110580e9a33cfb0c6e9cbdce630262d5b5d4c1)
- Fixed id sorting [`7cec2a0`](https://git.odit.services/lfk/frontend/commit/7cec2a00c513678cae8643ef3905d566a538bde0)
- Import add to datatable [`2a91562`](https://git.odit.services/lfk/frontend/commit/2a915620c9adcb481be53e3c919bd5f14a5108ee)
- Scans deletion [`6d9d8a4`](https://git.odit.services/lfk/frontend/commit/6d9d8a4724135df98ed1bec74b0cc20a8bdbda53)
- fix(DeleteRunnerModal): ESC key [`1df505e`](https://git.odit.services/lfk/frontend/commit/1df505ea00968f65e7aff85baa80f8935fd2eb7b)
- Versionbuilder [`1613ae7`](https://git.odit.services/lfk/frontend/commit/1613ae7de6e84aa5f9df6774a8e820fcf515b14a)
- Fixed edit dispatch [`c681570`](https://git.odit.services/lfk/frontend/commit/c68157013431eeb1150e37fea87da02d5f7b032e)
- ScansOverview: use CardRunner link [`4b171fd`](https://git.odit.services/lfk/frontend/commit/4b171fd04f4fc895f9919294858e546686504c65)
- Added custom filter to scan overview [`2c198cf`](https://git.odit.services/lfk/frontend/commit/2c198cfde89363142c1c958e6961b7823196e04e)
- Disable sort for actions [`fc2c290`](https://git.odit.services/lfk/frontend/commit/fc2c2907c43e5b1fa192b571cf93b2f2e492158f)
- Delete modal logic [`da33005`](https://git.odit.services/lfk/frontend/commit/da3300562a25cf39945a94f873ec27f0d5fb9dc0)
- Module [`9ae5e62`](https://git.odit.services/lfk/frontend/commit/9ae5e62e5d84097082bc29fe20b329f6e614f6ed)
- new license file version [CI SKIP] [`ed1caa7`](https://git.odit.services/lfk/frontend/commit/ed1caa7be7673ef08f9c0074101f3cfb52963ab7)
- Merge pull request 'experiment/tanstack' (#172) from experiment/tanstack into dev [`d88f3a5`](https://git.odit.services/lfk/frontend/commit/d88f3a5a2737921536c5eb109734abafd7609e51)
- Removed pnpm store [`eb13f03`](https://git.odit.services/lfk/frontend/commit/eb13f038a1990ed2966552ab1d36df4a76e5a99a)
- dependency update, drop focusTrap, first tanstack experiment in RunnersOverview [`9bec95e`](https://git.odit.services/lfk/frontend/commit/9bec95ede80908e45ad0f18a66dbb45784f7e33e)
- Added translations [`dcabed4`](https://git.odit.services/lfk/frontend/commit/dcabed4e93134a1f04305c47536f1cc93b0cfb31)
- new license file version [CI SKIP] [`d0fe6a2`](https://git.odit.services/lfk/frontend/commit/d0fe6a2e8513638c4d8e2b3dafd48b91490eab3b)
- Bumped pnpm to 8 [`ee91748`](https://git.odit.services/lfk/frontend/commit/ee91748b3c7c2fb4f196d76121713ac42465b9dd)
- drop old datatables [`cb5f2b7`](https://git.odit.services/lfk/frontend/commit/cb5f2b73d0a5f71875f48d381382919f9bee364e)
- wip: pagination [`a59dbbe`](https://git.odit.services/lfk/frontend/commit/a59dbbe50ede47e0dada906d10887cc6b1ae3264)
- Added sort [`842248e`](https://git.odit.services/lfk/frontend/commit/842248e4c43bb94a0e73981d2d848e0723687932)
- cleanup headers [`5ecf838`](https://git.odit.services/lfk/frontend/commit/5ecf838dd231269c3c4f0d1e3376ff157457b04a)
- Moved filter function to shared [`2304b12`](https://git.odit.services/lfk/frontend/commit/2304b12c1cca6a64573223906ab0561ba9050ec5)
- Group filters [`0283df2`](https://git.odit.services/lfk/frontend/commit/0283df22c84740dd978e09120985215b65d7a12a)
- feat(RunnersOverview): row selection [`6993511`](https://git.odit.services/lfk/frontend/commit/6993511c67cc81303a1eece7fc2a4218dc818afc)
- Basic details and delete buttons [`9363773`](https://git.odit.services/lfk/frontend/commit/9363773fa1910975d48746572fe166363986c6ab)
- Added delete cards button [`70307a9`](https://git.odit.services/lfk/frontend/commit/70307a9e8272f22801c6765c947f8576cc1d1102)
- ScansOverview: add ThFilterTrack [`008835c`](https://git.odit.services/lfk/frontend/commit/008835c24f833aebbecc53921b0901a76f25bf06)
- Yeeted old datatable references [`8925261`](https://git.odit.services/lfk/frontend/commit/89252619b1f6d478c287178bfffc573cf574a8fa)
- Added filter [`a9cdac4`](https://git.odit.services/lfk/frontend/commit/a9cdac4f74a80afe3d0ce677ed5256201b75b29b)
- Moved sort onclick to header only [`3de7b63`](https://git.odit.services/lfk/frontend/commit/3de7b632e058e9ca9ab951de9e86cc2a40eceb68)
- Switched drone to pnpm cache [`d79608e`](https://git.odit.services/lfk/frontend/commit/d79608edbb7ca68b0a26f6c021a141373308081f)
- pagination size [`45a7a90`](https://git.odit.services/lfk/frontend/commit/45a7a90cb8ee933d064c5891b3532cb1ece38d38)
- RunnersOverview: add filter keyboard support [`9415584`](https://git.odit.services/lfk/frontend/commit/94155845f020679e8765d198b5ab4735e1d1142c)
- RunnersOverview: pass selectedRunners to actions [`d4ab76e`](https://git.odit.services/lfk/frontend/commit/d4ab76ea1be1789aaa53ca57d33b2356c6804ffd)
- fix pagination next prev [`cac851f`](https://git.odit.services/lfk/frontend/commit/cac851f2b19dafa01b08d1da612e34cbe3eb4133)
- Moved docker to pnpm with cache [`4cbd265`](https://git.odit.services/lfk/frontend/commit/4cbd26580e51b854fdfd369056f24a30b04100a1)
- fix: TracksOverview [`c799088`](https://git.odit.services/lfk/frontend/commit/c7990882cfa4ac6c3a2ffa696dcbf3b62790c1fe)
- Table header i18n [`070a20a`](https://git.odit.services/lfk/frontend/commit/070a20a2e599e9f00b1bd0e944f9d68d08dc1cba)
- default to 50 rows pagination [`dc866dd`](https://git.odit.services/lfk/frontend/commit/dc866dd5403ae681193c5e325b1f6d4068f80b61)
- improved tablefilters/groupFilter [`3abf608`](https://git.odit.services/lfk/frontend/commit/3abf608b15fcfc2cf65b705d28d394917e7ac333)
- Updated group name conversion [`9111ad1`](https://git.odit.services/lfk/frontend/commit/9111ad147c7537036de834f80db6e557d8d6f0d9)
- show certificate, runnercard, sponsoring contract section [`49c2cd5`](https://git.odit.services/lfk/frontend/commit/49c2cd5c4b32e1b75fe0dae48c1b470608936704)
- add basic table styling [`c5e8409`](https://git.odit.services/lfk/frontend/commit/c5e84090795636c7a80d6cabe46168e436d40f50)
- Cards deleted migrations [`ef077b4`](https://git.odit.services/lfk/frontend/commit/ef077b4e6ad722e6d1c9efd5060165f649fd94c9)
- ignore pnpm store from now on [`fe62ad5`](https://git.odit.services/lfk/frontend/commit/fe62ad5539bc94b242c30c2952bf4c115cc9abfd)
- ammendme [`d31fe23`](https://git.odit.services/lfk/frontend/commit/d31fe2363b0f167b937f7d67908e75c709b324d8)
- updated default table row count [`245db06`](https://git.odit.services/lfk/frontend/commit/245db0617306bab23993caf00dc5ba0405e95625)
- fix: checkbox styling [`64db553`](https://git.odit.services/lfk/frontend/commit/64db5531858b2275d27c53a347da6c0416a278e8)
- Added distance conversion [`333214a`](https://git.odit.services/lfk/frontend/commit/333214aa8f945975ee2dcbd1cfa285e5e75c9d02)
- Added filterfunctions [`a9a965d`](https://git.odit.services/lfk/frontend/commit/a9a965d6981b07b02e3f752e65e1e5c4e9df7147)
- card/ThFilterRunner: text align left [`7083b3d`](https://git.odit.services/lfk/frontend/commit/7083b3d8d2c81fa4ab14f21d379befbb2dab5b6c)
- import [`0240e1d`](https://git.odit.services/lfk/frontend/commit/0240e1dca2e162b8c79f294f5c651b4a38455235)
- fix: table sort button + search style [`57dce34`](https://git.odit.services/lfk/frontend/commit/57dce34fc5b04c297c5341db24ec47c2f1d39595)
- No longer switching pnpm store path [`e5241d6`](https://git.odit.services/lfk/frontend/commit/e5241d619beda90d271485b6c09142b1e7b9b5f1)
- Reactivated generate cards modal [`8f33640`](https://git.odit.services/lfk/frontend/commit/8f33640bec8f79987b1319fb0765535fe493d496)
- Always load bulk created cards [`f89023e`](https://git.odit.services/lfk/frontend/commit/f89023e24ab30c90a8cacf2e9590d1dfa1a743d5)
- ScansOverview: fallback for manual scans [`1ec9556`](https://git.odit.services/lfk/frontend/commit/1ec9556aa64b996d0bdd9cd3ee50a767a154f5f6)
- fix min-w th [`b35375c`](https://git.odit.services/lfk/frontend/commit/b35375c929ad8d525b2761c321ecef0399917828)
- Close modal on delete and import toastify [`03b7ada`](https://git.odit.services/lfk/frontend/commit/03b7ada5ef3cbf64f178d3d0d88c448d76a42885)
- Fixed styling [`13254b2`](https://git.odit.services/lfk/frontend/commit/13254b24dd75ca3eb47fd53d5368f047545e8420)
- Implemented table buttons [`11a56f8`](https://git.odit.services/lfk/frontend/commit/11a56f87e89866bd9b2f7029ef683fd139d60aa4)
- RunnersOverview: disable debug log [`2c992a0`](https://git.odit.services/lfk/frontend/commit/2c992a0e63fa72055d28785fe7f5c21fd26f1fea)
- RunnersOverview: TopActionSection: add margin top [`88f96ac`](https://git.odit.services/lfk/frontend/commit/88f96acc3c31c748d3da29b4564dd681a89208b9)
- Unused filter function value [`38d3e19`](https://git.odit.services/lfk/frontend/commit/38d3e1912cd6d5362db236c34e0c602b52060b73)
- Make the linter happy [`fbc14fd`](https://git.odit.services/lfk/frontend/commit/fbc14fd7b47009bf64a8824e7b60373eea031e3f)
- Dsable column filter for distance [`67eff0e`](https://git.odit.services/lfk/frontend/commit/67eff0eda9b7d49d1abe784247afdbfbd7a76b00)
- getPaginationRowModel [`238082b`](https://git.odit.services/lfk/frontend/commit/238082b657998f513111e832556926decbe84e21)
- -&gt; onkeyup [`aecbabe`](https://git.odit.services/lfk/frontend/commit/aecbabe52221c0ab8d94a42dc8ac42f4f397fde8)
- removed unused import [`9811ede`](https://git.odit.services/lfk/frontend/commit/9811ede3b23a488a49962a43dae5abad60f93341)
- drop redundant button role [`e7eddb4`](https://git.odit.services/lfk/frontend/commit/e7eddb4f08632cc803b8752e3561319fab538843)
- Filter id as equals [`2699b06`](https://git.odit.services/lfk/frontend/commit/2699b06d7c42608b1b3ce2037908a78143038441)
- debug table [`fd0d45f`](https://git.odit.services/lfk/frontend/commit/fd0d45f721396787ad35fca2a7fd0f26d98b23e9)
- add TODO for ScansOverview status filter [`9505c2b`](https://git.odit.services/lfk/frontend/commit/9505c2b03005f4e667082cb4527fea863dbbf7fc)
#### [0.17.3](https://git.odit.services/lfk/frontend/compare/0.17.2...0.17.3)
> 15 March 2023
- dependency fixes [`3ea7a01`](https://git.odit.services/lfk/frontend/commit/3ea7a015a9beba3c2e4d3eb966f24ff6d4ac786e)
- 🚀RELEASE v0.17.3 [`85705b6`](https://git.odit.services/lfk/frontend/commit/85705b6e684d0c41d3dc770cef7bffb199101576)
- set pnpm to @7 [`4432941`](https://git.odit.services/lfk/frontend/commit/44329413ed2ca23f74e86db041b2c25b2b1c2a2b)
#### [0.17.2](https://git.odit.services/lfk/frontend/compare/0.17.1...0.17.2)
> 15 March 2023
- new license file version [CI SKIP] [`00359d2`](https://git.odit.services/lfk/frontend/commit/00359d25c1bd3efdd6365bf284b3c07634049399)
- 🚀RELEASE v0.17.2 [`46db68a`](https://git.odit.services/lfk/frontend/commit/46db68ab229dc740dfff8835ef916f2c2e629b27)
- improved ThFilterGroup style [`f917018`](https://git.odit.services/lfk/frontend/commit/f917018fd92a8a5b034f735ac8b6e41995044317)
#### [0.17.1](https://git.odit.services/lfk/frontend/compare/0.17.0...0.17.1)
> 15 March 2023
- Revert "package dependency fixes, bumps, lockfile update" [`d8a3063`](https://git.odit.services/lfk/frontend/commit/d8a30637351e164599e07a6473d9a1d2b08d245d)
- 🚀RELEASE v0.17.1 [`7b420c4`](https://git.odit.services/lfk/frontend/commit/7b420c430d27bf0fc85a4297780164a593fc1be3)
#### [0.17.0](https://git.odit.services/lfk/frontend/compare/0.16.5...0.17.0)
> 15 March 2023
- new license file version [CI SKIP] [`61328d2`](https://git.odit.services/lfk/frontend/commit/61328d20ed2cfd1df7d3c32767f9c64154879d6d)
- wip: pnpm + node version [`732b2f0`](https://git.odit.services/lfk/frontend/commit/732b2f061e465bd08cf34c58d8cd6b021ba25dce)
- package dependency fixes, bumps, lockfile update [`2c73b98`](https://git.odit.services/lfk/frontend/commit/2c73b9862d401dac15021eed3f7847d46132a8ed)
- Fixed runners not showing up [`75bc89c`](https://git.odit.services/lfk/frontend/commit/75bc89ca3020c48f490c7602374616bd9461e78f)
- add ThFilterRunner [`3a3e2f7`](https://git.odit.services/lfk/frontend/commit/3a3e2f71575d3a0e39a5e13b05cff932b8683ac9)
- fix styling for table filters th border [`bea57aa`](https://git.odit.services/lfk/frontend/commit/bea57aa03acaaaa4b1860b30228dd5d1298317f8)
- You can now create cards from runners by searching via #runnerid [`e8a0ad6`](https://git.odit.services/lfk/frontend/commit/e8a0ad6647ab39252865f62b755f27c34ac2d243)
- remodelled for early return [`b869b5f`](https://git.odit.services/lfk/frontend/commit/b869b5fd2a01955fb21f936fa38eb5a9648e7de3)
- 🚀RELEASE v0.17.0 [`6491af1`](https://git.odit.services/lfk/frontend/commit/6491af19e375cbeba7ddd94e463b4dfe308a70a8)
- Wow this api is fun [`32a9074`](https://git.odit.services/lfk/frontend/commit/32a9074963cce3328f14b1f981ddd5ee49df0008)
- Fixed double space in label [`92b89cc`](https://git.odit.services/lfk/frontend/commit/92b89cc4d88c9d5625c2ddf7c81c98494f7f5271)
- UsersOverview: drop pfp [`30991d5`](https://git.odit.services/lfk/frontend/commit/30991d5364a09d517b2115a7e9ea3fbf1fe2e57d)
- Switched license generation to cache registry and pnpm [`0a6d92a`](https://git.odit.services/lfk/frontend/commit/0a6d92a1f3c5562f11562c433b3a04e3eaae3da4)
- Pinned pnpm in dockerfile, thx @philipp [`3a576d1`](https://git.odit.services/lfk/frontend/commit/3a576d1073ee503b68100e01054a1756bab62805)
- Pinned ci node version [`b30b98b`](https://git.odit.services/lfk/frontend/commit/b30b98b521eda2bc7fc055097546f716e90d92ef)
- Fixed pnpm being called without being installed [`43d82a2`](https://git.odit.services/lfk/frontend/commit/43d82a2af04af49c2169f78a0d0f27ef7e4d7558)
- Merge pull request 'bugfix/162-create_card_modal' (#163) from bugfix/162-create_card_modal into dev [`6a4495b`](https://git.odit.services/lfk/frontend/commit/6a4495b8131a31cd48a608c2275e80494d0a0fb4)
- Removed unused log [`268b1b1`](https://git.odit.services/lfk/frontend/commit/268b1b1d9830de196d1d95345d7a2467bbf19eb6)
- Merge pull request 'filter by runner full names + "#&lt;ID&gt;"' (#160) from feature/159-cardsoverview-filter-for-runner-full-names-and-id into dev [`0625937`](https://git.odit.services/lfk/frontend/commit/0625937068f0786078ffd29b9c8bb54949350b6c)
- UsersOverview: change profilepic scaling [`5cc8b08`](https://git.odit.services/lfk/frontend/commit/5cc8b0811cf290f97a4399b23c5ea4d961a5a91c)
#### [0.16.5](https://git.odit.services/lfk/frontend/compare/0.16.4...0.16.5)
> 14 March 2023
- 🚀RELEASE v0.16.5 [`3680533`](https://git.odit.services/lfk/frontend/commit/3680533eefef042fc77246dd3d374aafe10c428f)
- new license file version [CI SKIP] [`405dfa0`](https://git.odit.services/lfk/frontend/commit/405dfa0c34ba87fc450c22e0e9974f92c4cdeffe)
#### [0.16.4](https://git.odit.services/lfk/frontend/compare/0.16.3...0.16.4)
> 14 March 2023
- fix: OrgDetail: clicking on address will toggle selfservice [`#158`](https://git.odit.services/lfk/frontend/issues/158)
- 🚀RELEASE v0.16.4 [`5c2d154`](https://git.odit.services/lfk/frontend/commit/5c2d154ad180ce7916605871c63e2f5ac4428250)
#### [0.16.3](https://git.odit.services/lfk/frontend/compare/0.16.2...0.16.3)
> 23 February 2023
- 🚀RELEASE v0.16.3 [`f9cfd6b`](https://git.odit.services/lfk/frontend/commit/f9cfd6bd063b01a584774854d8fb5eab96f99528)
- Bumped vite build targets [`5fe4763`](https://git.odit.services/lfk/frontend/commit/5fe47634e8980e77b65c05f213c475cf49273609)
- new license file version [CI SKIP] [`a659091`](https://git.odit.services/lfk/frontend/commit/a6590910cfdc5e91fba91c1bc9237e407ef15fd2)
- Merge pull request 'feature/156-pdf_names' (#157) from feature/156-pdf_names into dev [`ad454c3`](https://git.odit.services/lfk/frontend/commit/ad454c386cbf11abc59d41d269d1a0ef7442c9ed)
- Added ids for generated pdfs [`0b2c296`](https://git.odit.services/lfk/frontend/commit/0b2c296de069a4ef302c5535de01bc18236675bc)
#### [0.16.2](https://git.odit.services/lfk/frontend/compare/0.16.1...0.16.2)
> 23 February 2023
- Fixed scanmodal [`#154`](https://git.odit.services/lfk/frontend/issues/154)
- 🚀RELEASE v0.16.2 [`0e85940`](https://git.odit.services/lfk/frontend/commit/0e85940cba292cbccd1ec038aa24f4a719382c19)
- Merge pull request 'feature/147-cardoverview_performance' (#153) from feature/147-cardoverview_performance into dev [`8d479c3`](https://git.odit.services/lfk/frontend/commit/8d479c32f82938904aee6a10deb80fea85487e4b)
- Merge pull request 'Fixed scanmodal' (#155) from bugfix/154-scan_select_runner_by_id into dev [`549785c`](https://git.odit.services/lfk/frontend/commit/549785cf7d1c9edc1be37dc745b97096232a08ca)
- i18n [`ca6da15`](https://git.odit.services/lfk/frontend/commit/ca6da15ef761184a55b18d56f749f660a32cbb83)
- Bsic datatable conversion [`757655e`](https://git.odit.services/lfk/frontend/commit/757655ea63b3667bc4612ae1595eb52910b61137)
- 1st datatable try with @vincjo/datatables [`81b8fbf`](https://git.odit.services/lfk/frontend/commit/81b8fbf4e341e6f2998a6e9e2053972c5c225021)
- Dasboard Cards redesign [`eb1c17e`](https://git.odit.services/lfk/frontend/commit/eb1c17e3ac7e8f5e7310a90421fc9db3ed15c497)
- set .phone to null if empty [`da6dd55`](https://git.odit.services/lfk/frontend/commit/da6dd55d139a672fa50204eabdca67d9740614a0)
- add group filtering to table [`14d64b6`](https://git.odit.services/lfk/frontend/commit/14d64b6070d98e6368da5709e9ff8221e8a621c7)
- formatting [`24d0747`](https://git.odit.services/lfk/frontend/commit/24d074752f1c5dc1a14b075ac14b448d7e129376)
- RunnersOverview loading fix [`2e075ea`](https://git.odit.services/lfk/frontend/commit/2e075eafab5c4d78fd9aa9d66834b477b2685bfc)
- Added old formatting for runner and status [`df63c23`](https://git.odit.services/lfk/frontend/commit/df63c2388da359dec9ed9968bc9f970be7092a45)
- Added custom status filter [`f0a2b28`](https://git.odit.services/lfk/frontend/commit/f0a2b2859fa18426a454b7d9d6dd22dfdfe7ce27)
- Basic checkbox fix [`1337676`](https://git.odit.services/lfk/frontend/commit/1337676e0894c46da0b6dcb7553e5ea8f88d0c14)
- Fixed all filter [`8dfa19f`](https://git.odit.services/lfk/frontend/commit/8dfa19fa0f9897c61342ece956df88731c7aa861)
- improved runner search [`1b0cd5b`](https://git.odit.services/lfk/frontend/commit/1b0cd5b90bcceb92627c6b7cdcdd7792ed81b50f)
- set table-layout:fixed + display when loaded [`65e8998`](https://git.odit.services/lfk/frontend/commit/65e89988943807c1606a8b6aea49564b13d52537)
- Trigger edit modal [`32ddb66`](https://git.odit.services/lfk/frontend/commit/32ddb66fc8d8cd689f1104759812f4cee4b7a613)
- cleaned up table search [`08047a9`](https://git.odit.services/lfk/frontend/commit/08047a93073c32f5dd7a8e958400ae8a5b7f4035)
- Fixed edit update bug [`0feee0a`](https://git.odit.services/lfk/frontend/commit/0feee0ae2fb6d8dba0b6fd72cedc0712dc749511)
- fix: z-index on action buttons [`224034d`](https://git.odit.services/lfk/frontend/commit/224034dcc6263d3b0a8ea20045e435142d8ed2af)
- rename: ThFilterGroup -&gt; ThFilterStatus [`2a6a399`](https://git.odit.services/lfk/frontend/commit/2a6a39916a03c0466e63354e9f5ad7924cb59b6b)
- new license file version [CI SKIP] [`0e5490f`](https://git.odit.services/lfk/frontend/commit/0e5490f1c84217a5a6d5c8745c4667b32ca65e1a)
- new license file version [CI SKIP] [`026d3d4`](https://git.odit.services/lfk/frontend/commit/026d3d41c1b976a4dc7c733576a6a9e8d4b13b78)
- Updated breakpoints [`452d010`](https://git.odit.services/lfk/frontend/commit/452d0101838d72bff7d588a953faae028e2ff819)
- Tailwind bump [`a101873`](https://git.odit.services/lfk/frontend/commit/a101873eb0946b284a11a5081642711f5087da14)
- Fixed checkbox show [`0900c26`](https://git.odit.services/lfk/frontend/commit/0900c2691e4cfe5046e8ae186c8ac8884c90abd6)
- Removed unused console log [`aafc4c8`](https://git.odit.services/lfk/frontend/commit/aafc4c8d62a7a0a493c8bd60149f90c842534bdd)
- i18n import [`6fe134a`](https://git.odit.services/lfk/frontend/commit/6fe134afc8bfef4e7470b7e53b9312b172a7322b)
- Merge pull request 'fix: RunnerDetail: set .phone to null if empty' (#152) from bugfix/151-runnerdetail--cannot-unset-phone-number into dev [`329c1cc`](https://git.odit.services/lfk/frontend/commit/329c1cc037a43c818ba3b6c72581d29586d76232)
- Merge pull request 'feature/146-runner-table-performance-data-table' (#150) from feature/146-runner-table-performance-data-table into dev [`b82d638`](https://git.odit.services/lfk/frontend/commit/b82d638de1aa1f72aada212cf3e4147d808b4fcf)
- Merge pull request 'feature/148-dashboard_statscards' (#149) from feature/148-dashboard_statscards into dev [`fd1a06b`](https://git.odit.services/lfk/frontend/commit/fd1a06b3595b3713ad474e623c74105125602d46)
- Fixed top checkbox state [`3d2acb6`](https://git.odit.services/lfk/frontend/commit/3d2acb692a28c116790248679e238fb562b24ac5)
#### [0.16.1](https://git.odit.services/lfk/frontend/compare/0.16.0...0.16.1)
> 15 February 2023
- fix: donor detail: sponsorings: unset middlename will show as "null" [`#145`](https://git.odit.services/lfk/frontend/issues/145)
- 🚀RELEASE v0.16.1 [`4499480`](https://git.odit.services/lfk/frontend/commit/449948050b8673d43a8dfbb225c3198e4bbb3c7b)
#### [0.16.0](https://git.odit.services/lfk/frontend/compare/0.15.6...0.16.0)
> 3 February 2023
- First page for statsclients [`f299617`](https://git.odit.services/lfk/frontend/commit/f299617c600d2bba7b4405c7c3acae9fd93aefa8)
- 🚀RELEASE v0.16.0 [`75684ef`](https://git.odit.services/lfk/frontend/commit/75684efa1ae0edb4b4d414757c5acf2a77c572e5)
- Basic statsclient detail [`0215860`](https://git.odit.services/lfk/frontend/commit/02158605be824e5ac21a6284731138190988c794)
- Updated Add modal [`f679330`](https://git.odit.services/lfk/frontend/commit/f679330466205e6480cd7f2b7c2b4fdc41c51525)
- Switched drone to kaniko [`1c98005`](https://git.odit.services/lfk/frontend/commit/1c980059cff5c87c452428b53513507c2339451f)
- Re-added copy modal [`fecb07e`](https://git.odit.services/lfk/frontend/commit/fecb07ee373dcaaeaea69fdf8d4c6ee2c257c89c)
- Added Statsclients to sidebar [`068076d`](https://git.odit.services/lfk/frontend/commit/068076dd47373c673a25e730cb8a57c686682810)
- Fixed imports and naming [`f3cc07c`](https://git.odit.services/lfk/frontend/commit/f3cc07c009ed0a34e61f1aad47a1a31778145439)
- new license file version [CI SKIP] [`2c4f27a`](https://git.odit.services/lfk/frontend/commit/2c4f27a943bb35be6728bb49bd5c2263cba78165)
- Merge pull request 'feature/143-beamershow_clients' (#144) from feature/143-beamershow_clients into dev [`53b7dec`](https://git.odit.services/lfk/frontend/commit/53b7dec7cd516c908d45591b855f4be09371f9b1)
- Updated deletion modal [`93fc7c2`](https://git.odit.services/lfk/frontend/commit/93fc7c2e83f78dd88f15d9246127bb9e69f1a8ee)
- Updated mounted variables [`674e6a9`](https://git.odit.services/lfk/frontend/commit/674e6a90ec23dde9377bea64c14a50e41ffa450d)
- Removed Key after creation [`e10c648`](https://git.odit.services/lfk/frontend/commit/e10c6480a504338b21e30fdf2577e5b6c3b635db)
- Updated docker base images [`9767553`](https://git.odit.services/lfk/frontend/commit/976755338b8621064f9a73147aa600af1f77cd51)
- Added translation [`96c55db`](https://git.odit.services/lfk/frontend/commit/96c55db63dbfed92b78ff0e7bdab7a8cce4d76e9)
- Pinned versions [`cff112d`](https://git.odit.services/lfk/frontend/commit/cff112d705a74a135286943298f3f344341325ac)
- Tailwind bump [`e0cbfb0`](https://git.odit.services/lfk/frontend/commit/e0cbfb000bee59a71e06bd58a9c7ef6a0fc7091d)
- Added missing translation [`19a333d`](https://git.odit.services/lfk/frontend/commit/19a333d7bda525fbcb3c68f3cbf85a4f925a9707)
- Bumped apiclient [`c28f1ee`](https://git.odit.services/lfk/frontend/commit/c28f1ee0bc4456595c21858f38e52ed6f16871c5)
- new license file version [CI SKIP] [`3a66f4c`](https://git.odit.services/lfk/frontend/commit/3a66f4c862db9f35c223cc7007b0560fef4e1d63)
- Bumped apiclient [`28cbc5b`](https://git.odit.services/lfk/frontend/commit/28cbc5b98ca09657100e1740b83aa2617243b26b)
- Ignore pnpm lock [`2d8c4c1`](https://git.odit.services/lfk/frontend/commit/2d8c4c1698a1675f618e85e678012f310f87c6ee)
#### [0.15.6](https://git.odit.services/lfk/frontend/compare/0.15.5...0.15.6) #### [0.15.6](https://git.odit.services/lfk/frontend/compare/0.15.5...0.15.6)
> 19 July 2021
- 🚀RELEASE v0.15.6 [`9fc4ad6`](https://git.odit.services/lfk/frontend/commit/9fc4ad63c4f77b46d645e83c94b51747b91247b8)
- Fixed donations getting reduced to the first one on certificates [`2391668`](https://git.odit.services/lfk/frontend/commit/2391668a25a1e11a1409df572d77ad1635070fbc) - Fixed donations getting reduced to the first one on certificates [`2391668`](https://git.odit.services/lfk/frontend/commit/2391668a25a1e11a1409df572d77ad1635070fbc)
- new license file version [CI SKIP] [`97054a7`](https://git.odit.services/lfk/frontend/commit/97054a71c1ab8a045762a55148124965c6994373) - new license file version [CI SKIP] [`97054a7`](https://git.odit.services/lfk/frontend/commit/97054a71c1ab8a045762a55148124965c6994373)
@@ -1184,7 +1515,7 @@ All notable changes to this project will be documented in this file. Dates are d
- init [`32357ec`](https://git.odit.services/lfk/frontend/commit/32357ece0a7195ea1135c9c3e4c6c84323f95b4d) - init [`32357ec`](https://git.odit.services/lfk/frontend/commit/32357ece0a7195ea1135c9c3e4c6c84323f95b4d)
- tmp [`1b7173c`](https://git.odit.services/lfk/frontend/commit/1b7173cda9134ee8058a00bdc030defa80d46bfc) - tmp [`1b7173c`](https://git.odit.services/lfk/frontend/commit/1b7173cda9134ee8058a00bdc030defa80d46bfc)
- Login - move to env.js import [`8ef0b21`](https://git.odit.services/lfk/frontend/commit/8ef0b21819309752c573d0485f6514152fb684e6) - Login - move to env.js import [`8ef0b21`](https://git.odit.services/lfk/frontend/commit/8ef0b21819309752c573d0485f6514152fb684e6)
- initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae) - initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae)
- Initial license export [`4c96b9a`](https://git.odit.services/lfk/frontend/commit/4c96b9a3e04dbb7c021c71aa8828a29248509fbe) - Initial license export [`4c96b9a`](https://git.odit.services/lfk/frontend/commit/4c96b9a3e04dbb7c021c71aa8828a29248509fbe)
- 🚚 move to tinro svelte router [`a50ea15`](https://git.odit.services/lfk/frontend/commit/a50ea15b38023b867a9f7757e973184cbcdd2457) - 🚚 move to tinro svelte router [`a50ea15`](https://git.odit.services/lfk/frontend/commit/a50ea15b38023b867a9f7757e973184cbcdd2457)
- new Dashboard [`7270ce9`](https://git.odit.services/lfk/frontend/commit/7270ce9d32869abd4f6ac65ab7c2c87363633cbe) - new Dashboard [`7270ce9`](https://git.odit.services/lfk/frontend/commit/7270ce9d32869abd4f6ac65ab7c2c87363633cbe)

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
const fs = require('fs'); import fs from 'fs'
// get all language files // get all language files
const files = fs.readdirSync('./src/locales/'); const files = fs.readdirSync('./src/locales/');
files.forEach((f) => { files.forEach((f) => {

View File

@@ -1,56 +1,61 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "0.15.6", "version": "1.1.0",
"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", "auto-changelog": "2.4.0",
"auto-changelog": "2.2.1", "autoprefixer": "10.4.14",
"autoprefixer": "10.2.5", "postcss": "8.4.21",
"check-password-strength": "2.0.2", "release-it": "15.10.1",
"csvtojson": "2.0.10", "svelte-select": "3.17.0",
"gridjs": "3.4.0", "tailwindcss": "3.3.1",
"html-minifier": "4.0.0", "vite": "4.2.1"
"localforage": "1.9.0", },
"marked": "2.0.3", "release-it": {
"postcss": "8.2.10", "git": {
"release-it": "14.6.1", "commit": true,
"svelte": "3.37.0", "requireCleanWorkingDir": false,
"svelte-focus-trap": "1.2.0", "commitMessage": "🚀RELEASE v${version}",
"svelte-i18n": "3.3.9", "push": true,
"svelte-preprocess": "4.7.0", "tag": true,
"svelte-select": "3.17.0", "tagName": null,
"tailwindcss": "2.1.1", "tagAnnotation": "v${version}"
"tinro": "0.6.1", },
"toastify-js": "1.10.0", "npm": {
"validator": "13.5.2", "publish": false
"vite": "2.1.5", },
"xlsx": "0.16.9" "hooks": {
}, "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"
"release-it": { }
"git": { },
"commit": true, "dependencies": {
"requireCleanWorkingDir": false, "@odit/lfk-client-js": "1.1.0",
"commitMessage": "🚀RELEASE v${version}", "@paralleldrive/cuid2": "^2.2.0",
"push": false, "@tanstack/svelte-table": "^8.8.5",
"tag": true, "bwip-js": "^3.4.0",
"tagName": null, "check-password-strength": "2.0.7",
"tagAnnotation": "v${version}" "csvtojson": "2.0.10",
}, "gridjs": "3.4.0",
"npm": { "localforage": "1.10.0",
"publish": false "marked": "2.0.3",
}, "svelte": "3.58.0",
"hooks": { "svelte-i18n": "3.6.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" "tinro": "0.6.12",
} "toastify-js": "1.12.0",
} "validator": "13.9.0",
} "xlsx": "0.18.5"
},
"volta": {
"node": "19.7.0"
}
}

3950
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,14 +1,16 @@
<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";
import { createEventDispatcher } from "svelte";
export let bulk_modal_open; export let bulk_modal_open;
export let current_cards;
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
const dispatch = createEventDispatcher();
$: card_count = 0; $: card_count = 0;
$: is_card_count_valid = card_count > 0; $: is_card_count_valid = card_count > 0;
$: processed_last_submit = true; $: processed_last_submit = true;
@@ -34,7 +36,7 @@
text: $_("creating-blanco-cards"), text: $_("creating-blanco-cards"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, false) RunnerCardService.runnerCardControllerPostBlancoBulk(card_count, true)
.then((result) => { .then((result) => {
bulk_modal_open = false; bulk_modal_open = false;
// //
@@ -43,6 +45,7 @@
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
dispatch("created", {cards: result})
}) })
.catch((err) => { .catch((err) => {
// //
@@ -71,11 +74,11 @@
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_cards = current_cards.concat(result);
const toast = Toastify({ const toast = Toastify({
text: $_("generating-pdf"), text: $_("generating-pdf"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
dispatch("created", {cards: result})
fetch( fetch(
`${config.baseurl_documentserver}/cards?&download=true&key=${config.documentserver_key}`, `${config.baseurl_documentserver}/cards?&download=true&key=${config.documentserver_key}`,
{ {
@@ -133,7 +136,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,
@@ -9,24 +9,40 @@
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Select from "svelte-select"; import Select from "svelte-select";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
export let current_cards;
const getRunnerLabel = (option) => const dispatch = createEventDispatcher();
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;
(() => { (() => {
@@ -64,8 +80,7 @@
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_cards.push(result); dispatch("created", { cards: [result] });
current_cards = current_cards;
}) })
.catch((err) => { .catch((err) => {
// //
@@ -82,65 +97,81 @@
{#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 +183,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,26 +1,34 @@
<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 runner = {}; export let runner = {};
export let editable = {}; export let editable = {};
export let original_data = {}; export let original_data = {};
const getRunnerLabel = (option) => const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) => const filterRunners = (label, filterText, option) => {
label.toLowerCase().includes(filterText.toLowerCase()) || 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();
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 };
@@ -63,8 +71,7 @@
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_cards[current_cards.findIndex((c) => c.id === id)] = result; dispatch('dataUpdated', {card: result});
current_cards = current_cards;
}) })
.catch((err) => { .catch((err) => {
// //
@@ -81,7 +88,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

@@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
export let runner;
</script>
{#if !runner}
{$_("non-blanko")}
{:else}
<a href={`/runners/${runner.id}`}>
{#if runner.middlename}
{runner.firstname} {runner.middlename} {runner.lastname}
{:else}
{runner.firstname} {runner.lastname}
{/if}
</a>
{/if}

View File

@@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
export let enabled = false;
</script>
{#if enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("enabled")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("disabled")}</span
>
{/if}

View File

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

View File

@@ -6,276 +6,310 @@
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 InputElement from "../shared/InputElement.svelte";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import TableBottom from "../shared/TableBottom.svelte";
import TableActions from "../shared/TableActions.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import CardStatus from "./CardStatus.svelte";
import CardRunner from "./CardRunner.svelte";
import { onMount } from "svelte";
import { runnerFilter, statusFilter } from "../shared/tablefilters";
import DeleteCardModal from "./DeleteCardModal.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) { export const addCards = (cards) => {
if ( console.log(cards);
c.code.toLowerCase().includes(searchvalue_lowercase) || current_cards = current_cards.concat(...cards);
c.runner?.firstname.toLowerCase().includes(searchvalue_lowercase) || options.update((options) => ({
c.runner?.middlename.toLowerCase().includes(searchvalue_lowercase) || ...options,
c.runner?.lastname.toLowerCase().includes(searchvalue_lowercase) || data: current_cards,
should_display_based_on_id(c.id) }));
) { };
return true;
} $: dataLoaded = false;
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: selectedCards =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: active_delete = undefined;
$: cards_show = generate_cards.length > 0;
$: generate_cards = [];
const columns = [
{
accessorKey: "code",
header: () => $_("code"),
filterFn: `includesString`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
return renderComponent(CardRunner, { runner: info.getValue() });
},
filterFn: `runner`,
},
{
accessorKey: "enabled",
cell: (info) => {
return renderComponent(CardStatus, { enabled: info.getValue() });
},
header: () => $_("status"),
filterFn: `status`,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsAction: () => {
open_edit_modal(
current_cards[
current_cards.findIndex((r) => r.id == info.row.original.id)
]
);
},
deleteAction: () => {
active_delete =
current_cards[
current_cards.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes("CARD:DELETE"),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
runner: runnerFilter,
status: statusFilter,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
}); });
$: searchvalue = "";
$: searchvalue_lowercase = searchvalue.toLowerCase(); const table = createSvelteTable(options);
$: active_deletes = [];
$: cards_show = current_cards.some((r) => r.is_selected === true);
$: generate_cards = current_cards.filter((r) => r.is_selected === true);
const cards_promise = RunnerCardService.runnerCardControllerGetAll().then(
(val) => {
current_cards = val;
}
);
function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", ""));
}
return id.toString() === searchvalue;
}
const getRunnerLabel = (option) =>
option?.firstname + " " + (option?.middlename || "") + " " + (option?.lastname || "{$_('non-blanko')}");
function open_edit_modal(card) { function open_edit_modal(card) {
if(card.runner?.id){ const getRunnerLabel = (option) =>
option.firstname +
" " +
(option.middlename || "") +
" " +
option.lastname;
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; async function deleteCard(delete_card_id) {
$: rendered = filtered_cards; await RunnerCardService.runnerCardControllerRemove(delete_card_id, true);
let innerHeight = 0; current_cards = current_cards.filter((r) => r.id !== delete_card_id);
let ele; options.update((options) => ({
$: updateSlice(scrollTop); ...options,
$: innerHeight = `${filtered_cards.length * 25}px`; data: current_cards,
$: if (ele) updateSlice(); }));
function updateSlice() { Toastify({
const height = ele ? parseInt(ele.clientHeight) : 100; text: $_("card-deleted"),
const init = scrollTop / 25; duration: 3500,
const end = Math.ceil((scrollTop + height) / 25); backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
rendered = filtered_cards.slice(init, end + 15); }).showToast();
}
function updateScroll($event) {
scrollTop = $event.target.scrollTop;
} }
onMount(async () => {
let page = 0;
while (page >= 0) {
const cards = await RunnerCardService.runnerCardControllerGetAll(
page,
500
);
if (cards.length == 0) {
page = -2;
}
current_cards = current_cards.concat(...cards);
options.update((options) => ({
...options,
data: current_cards,
}));
dataLoaded = true;
page++;
}
console.log("All cards loaded");
});
</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:edit_modal_open bind:edit_modal_open
bind:runner bind:runner
bind:editable bind:editable
bind:original_data /> bind:original_data
on:dataUpdated={(event) => {
current_cards[
current_cards.findIndex((c) => c.id === event.detail.card.id)
] = event.detail.card;
current_cards = current_cards;
options.update((options) => ({
...options,
data: 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} <DeleteCardModal
delete_card={active_delete}
modal_open={active_delete != undefined}
on:delete={(event) => {
deleteCard(event.detail.id);
}}
/>
{#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">{$_('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} {:else if current_cards.length === 0}
{#if current_cards.length === 0} <CardsEmptyState />
<CardsEmptyState /> {:else}
{:else} <div class="h-12 mt-1">
<input {#if selected.length > 0}
type="search" <button
bind:value={searchvalue} type="button"
placeholder={$_('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"
aria-label={$_('datatable.search')} id="options-menu"
class="gridjs-input gridjs-search-input mb-4" /> on:click={async () => {
<div class="h-12"> const prom = [];
<GenerateRunnerCards for (const card of selectedCards) {
bind:cards_show prom.push(
bind:generate_cards /> await RunnerCardService.runnerCardControllerRemove(
</div> card.id,
<div true
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> )
<table class="divide-y divide-gray-200 w-full"> );
<thead class="bg-gray-50"> }
<tr> await Promise.all(prom);
<th for (const card of selectedCards) {
scope="col" current_cards = current_cards.filter((r) => r.id !== card.id);
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> }
<span options.update((options) => ({
on:click={() => { ...options,
const newstate = !current_cards.some((r) => r.is_selected === true); data: current_cards,
current_cards = current_cards.map((r) => { }));
r.is_selected = newstate; $table.resetRowSelection();
return r; }}
}); >
}} {$_("delete-cards")}
class="underline cursor-pointer select-none">{#if current_cards.some((r) => r.is_selected === true)} <svg
{$_('deselect-all')} xmlns="http://www.w3.org/2000/svg"
{:else}{$_('select-all')}{/if} fill="none"
</span> viewBox="0 0 24 24"
</th> stroke-width="1.5"
<th stroke="currentColor"
scope="col" class="w-5 h-5"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('code')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('runner')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('status')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 virtual-wrapper"
on:scroll={updateScroll}
style="height: 70vh; width:100%"
bind:this={ele}
> >
{#each filtered_cards as card, index} <path
{#if card.code stroke-linecap="round"
.toLowerCase() stroke-linejoin="round"
.includes( 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"
searchvalue.toLowerCase() />
) || card.runner?.firstname </svg>
.toLowerCase() </button>
.includes( {/if}
searchvalue.toLowerCase() <GenerateRunnerCards
) || card.runner?.middlename cards_show={selected.length > 0}
.toLowerCase() bind:generate_cards={selectedCards}
.includes( />
searchvalue.toLowerCase()
) || card.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(card.id)}
<tr data-rowid="card_{card.id}">
<td class="px-6 py-4 whitespace-nowrap">
<input
bind:checked={card.is_selected}
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">{card.code}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if card.runner}
<a
href="../runners/{card.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{card.runner.firstname}
{card.runner.middlename || ''}
{card.runner.lastname}</a>
{:else}{$_('non-blanko')}{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if card.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('enabled')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('disabled')}</span>
{/if}
</div>
</td>
{#if active_deletes[card.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[card.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
RunnerCardService.runnerCardControllerRemove(card.id, false).then(
(resp) => {
current_cards = current_cards.filter(
(obj) => obj.id !== card.id
);
Toastify({
text: $_('card-deleted'),
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
open_edit_modal(card);
}}
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</button>
{#if store.state.jwtinfo.userdetails.permissions.includes('CARD:DELETE')}
<button
on:click={() => {
active_deletes[card.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div> </div>
{/await} <div class="overflow-x-auto">
<table class="w-full">
<thead>
{#each $table.getHeaderGroups() as headerGroup}
<tr class="select-none">
<th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr>
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
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>
<TableBottom {table} {selected} />
{/if}
{/if} {/if}

View File

@@ -0,0 +1,128 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_card = {
id: 0,
code: "",
runner: {
firstname: "",
lastname: "",
},
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_card.id });
modal_open = false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M22 10v10a1 1 0 01-1 1H3a1 1 0 01-1-1V10h20zm0-2H2V4a1 1 0 011-1h18a1 1 0 011 1v4zm-7 8v2h4v-2h-4z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("confirm-delete")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-confirm-the-deletion-of-card")}
</p>
</div>
<div class="w-full">
{$_("card")} #{delete_card.code}<br />
<span class="inline-block">
{$_("runner")}:
{#if delete_card.runner}
<span class="inline-block"
>{delete_card.runner.firstname}
{delete_card.runner.lastname}</span
>
{:else}
{$_("non-blanko")}
{/if}</span
>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("delete")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,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

@@ -31,7 +31,7 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="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"> <table class="divide-y divide-gray-200 w-full">

View File

@@ -1,22 +1,224 @@
<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
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M2 4h2v16H2V4zm4 0h1v16H6V4zm2 0h2v16H8V4zm3 0h2v16h-2V4zm3 0h2v16h-2V4zm3 0h1v16h-1V4zm2 0h3v16h-3V4z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-donors")}
value={stats.total_donors}
href="/donors/"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"
/></svg
>
</StatCard>
<StatCard
title={$_("total-donation-count")}
value={stats.total_donations}
href="/donations/"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</StatCard>
<StatCard
title={$_("average-donation")}
value={`${(stats.average_donation / 100).toFixed(2)} €`}
href="/donations/"
>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 2a8 8 0 013.3 15.3A8 8 0 116.7 6.7 8 8 0 0114 2zm-3 7H9v1a2.5 2.5 0 00-.16 5h2.25a.5.5 0 010 1H7v2h2v1h2v-1a2.5 2.5 0 00.16-5H8.91a.5.5 0 010-1H13v-2h-2V9zm3-5a5.99 5.99 0 00-4.48 2.01 8 8 0 018.47 8.47A6 6 0 0014 4z"
/></svg
>
</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={$_("average-distance")}
value={`${(stats.average_distance / 1000).toFixed(2)} 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

@@ -5,18 +5,16 @@
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import DonationsEmptyState from "./DonationsEmptyState.svelte"; import DonationsEmptyState from "./DonationsEmptyState.svelte";
import AddDonationPaymentModal from "./AddDonationPaymentModal.svelte"; import AddDonationPaymentModal from "./AddDonationPaymentModal.svelte";
import { onMount } from "svelte";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: dataLoaded = false;
export let current_donations = []; export let current_donations = [];
export let payment_modal_open = false; export let payment_modal_open = false;
export let editable = {}; export let editable = {};
export let original_data = {}; export let original_data = {};
export let paid_amount_input = 0; export let paid_amount_input = 0;
const donations_promise = DonationService.donationControllerGetAll().then(
(val) => {
current_donations = val;
}
);
function should_display_based_on_id(id) { function should_display_based_on_id(id) {
if (searchvalue.toString().slice(-1) === "*") { if (searchvalue.toString().slice(-1) === "*") {
return id.toString().startsWith(searchvalue.replace("*", "")); return id.toString().startsWith(searchvalue.replace("*", ""));
@@ -26,210 +24,250 @@
function open_payment_modal(donation) { function open_payment_modal(donation) {
editable = Object.assign({}, donation); editable = Object.assign({}, donation);
original_data = Object.assign({}, donation); original_data = Object.assign({}, donation);
paid_amount_input = (donation.paidAmount/100).toFixed(2); paid_amount_input = (donation.paidAmount / 100).toFixed(2);
payment_modal_open = true; payment_modal_open = true;
} }
onMount(async () => {
let page = 0;
while (page >= 0) {
const donations = await DonationService.donationControllerGetAll(
page,
500
);
if (donations.length == 0) {
page = -2;
}
current_donations = current_donations.concat(...donations);
dataLoaded = true;
page++;
}
console.log("All donations loaded");
});
</script> </script>
<AddDonationPaymentModal bind:current_donations bind:original_data bind:editable bind:paid_amount_input bind:payment_modal_open /> <AddDonationPaymentModal
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:GET')} bind:current_donations
{#await donations_promise} bind:original_data
bind:editable
bind:paid_amount_input
bind:payment_modal_open
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:GET")}
{#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">donations are being loaded</p> <p class="font-bold">donations are being loaded</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:then} {:else if current_donations.length === 0}
{#if current_donations.length === 0} <DonationsEmptyState />
<DonationsEmptyState /> {:else}
{:else} <input
<input type="search"
type="search" bind:value={searchvalue}
bind:value={searchvalue} placeholder={$_("datatable.search")}
placeholder={$_('datatable.search')} aria-label={$_("datatable.search")}
aria-label={$_('datatable.search')} class="mb-4"
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"> <table class="divide-y divide-gray-200 w-full">
<tr> <thead class="bg-gray-50">
<th <tr>
scope="col" <th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> scope="col"
{$_('donor')} class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
</th> >
<th {$_("donor")}
scope="col" </th>
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th
{$_('runner')} scope="col"
</th> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
<th >
scope="col" {$_("runner")}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </th>
{$_('amount-per-kilometer')} <th
</th> scope="col"
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
scope="col" >
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> {$_("amount-per-kilometer")}
{$_('donation-amount')} </th>
</th> <th
<th scope="col"
scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> >
{$_('paid-amount')} {$_("donation-amount")}
</th> </th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
{$_('status')} >
</th> {$_("paid-amount")}
<th scope="col" class="relative px-6 py-3"> </th>
<span class="sr-only">{$_('action')}</span> <th
</th> scope="col"
</tr> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
</thead> >
<tbody class="divide-y divide-gray-200"> {$_("status")}
{#each current_donations as donation} </th>
{#if donation.donor.firstname <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_donations as donation}
{#if donation.donor.firstname
.toLowerCase()
.includes(searchvalue.toLowerCase()) || donation.donor.lastname
.toLowerCase() .toLowerCase()
.includes( .includes(searchvalue.toLowerCase()) || donation.runner?.firstname
searchvalue.toLowerCase() .toLowerCase()
) || donation.donor.lastname .includes(searchvalue.toLowerCase()) || donation.runner?.lastname
.toLowerCase() .toLowerCase()
.includes( .includes(searchvalue.toLowerCase()) || should_display_based_on_id(donation.id)}
searchvalue.toLowerCase() <tr data-rowid="donation_{donation.id}">
) || donation.runner?.firstname <td class="px-6 py-4 whitespace-nowrap">
.toLowerCase() <div class="flex items-center">
.includes( <a
searchvalue.toLowerCase() href="../donors/{donation.donor.id}"
) || donation.runner?.lastname class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
.toLowerCase() >{donation.donor.firstname}
.includes( {donation.donor.middlename || ""}
searchvalue.toLowerCase() {donation.donor.lastname}</a
) || should_display_based_on_id(donation.id)} >
<tr data-rowid="donation_{donation.id}"> </div>
<td class="px-6 py-4 whitespace-nowrap"> </td>
<div class="flex items-center"> <td class="px-6 py-4 whitespace-nowrap">
{#if donation.runner}
<div class="text-sm font-medium text-gray-900">
<a <a
href="../donors/{donation.donor.id}" href="../runners/{donation.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.donor.firstname} class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
{donation.donor.middlename || ''} >{donation.runner.firstname}
{donation.donor.lastname}</a> {donation.runner.middlename || ""}
{donation.runner.lastname}</a
>
</div> </div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.runner}
<div class="text-sm font-medium text-gray-900">
<a
href="../runners/{donation.runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.runner.firstname}
{donation.runner.middlename || ''}
{donation.runner.lastname}</a>
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('fixed-donation')}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.amountPerDistance}
<div class="text-sm font-medium text-gray-900">
{(donation.amountPerDistance / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('fixed-donation')}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{(donation.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{(donation.paidAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.status =="PAID"}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('paid')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('open')}</span>
{/if}
</td>
{#if active_deletes[donation.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[donation.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
DonationService.donationControllerRemove(donation.id, false).then(
(resp) => {
current_donations = current_donations.filter(
(obj) => obj.id !== donation.id
);
Toastify({
text: $_('donation-deleted'),
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else} {:else}
<td <div class="text-sm font-medium text-gray-900">
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> {$_("fixed-donation")}
<button </div>
on:click={() => {open_payment_modal(donation);}}
class="text-[#025a21] hover:text-green-900 mr-4">{$_('enter-payment')}</button>
<a
href="./{donation.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:DELETE')}
<button
on:click={() => {
active_deletes[donation.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if} {/if}
</tr> </td>
{/if} <td class="px-6 py-4 whitespace-nowrap">
{/each} {#if donation.amountPerDistance}
</tbody> <div class="text-sm font-medium text-gray-900">
</table> {(donation.amountPerDistance / 100)
</div> .toFixed(2)
{/if} .toLocaleString("de-DE", { valute: "EUR" })}
{:catch error} </div>
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> {:else}
<span class="inline-block align-middle mr-8"> <div class="text-sm font-medium text-gray-900">
<b class="capitalize">{$_('general_promise_error')}</b> {$_("fixed-donation")}
{error} </div>
</span> {/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{(donation.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{(donation.paidAmount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if donation.status == "PAID"}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("paid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("open")}</span
>
{/if}
</td>
{#if active_deletes[donation.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button
on:click={() => {
active_deletes[donation.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
DonationService.donationControllerRemove(
donation.id,
false
).then((resp) => {
current_donations = current_donations.filter(
(obj) => obj.id !== donation.id
);
Toastify({
text: $_("donation-deleted"),
duration: 500,
backgroundColor:
"linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button
on:click={() => {
open_payment_modal(donation);
}}
class="text-[#025a21] hover:text-green-900 mr-4"
>{$_("enter-payment")}</button
>
<a
href="./{donation.id}"
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:DELETE")}
<button
on:click={() => {
active_deletes[donation.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div> </div>
{/await} {/if}
{/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 {
DonorService DonorService
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
@@ -134,7 +134,7 @@
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -1,7 +1,7 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { DonorService } from "@odit/lfk-client-js"; import { DonorService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@@ -32,7 +32,7 @@
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside use:clickOutside
on:click_outside={cancelDelete}> on:click_outside={cancelDelete}>
<div <div

View File

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

View File

@@ -51,7 +51,7 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="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"> <table class="divide-y divide-gray-200 w-full">
@@ -119,7 +119,7 @@
{/if} {/if}
{#if donor.address.address1 !== null} {#if donor.address.address1 !== null}
{donor.address.address1}<br /> {donor.address.address1}<br />
{donor.address.address2 || ''}<br /> <!-- {donor.address.address2 || ''}<br /> -->
{donor.address.postalcode} {donor.address.postalcode}
{donor.address.city} {donor.address.city}
{donor.address.country} {donor.address.country}

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

@@ -30,7 +30,7 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="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"> <table class="divide-y divide-gray-200 w-full">

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

@@ -119,9 +119,9 @@
} }
} }
async function copy() { async function copy() {
if(!editable.registrationKey){ if (!editable.registrationKey) {
Toastify({ Toastify({
text: $_('you-have-to-save-your-changes-to-generate-a-link'), text: $_("you-have-to-save-your-changes-to-generate-a-link"),
duration: 500, duration: 500,
backgroundColor: backgroundColor:
"linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)", "linear-gradient(90deg, hsla(281, 37%, 45%, 1) 0%, hsla(1, 62%, 48%, 1) 100%)",
@@ -167,7 +167,8 @@
passed_orgs={[]} passed_orgs={[]}
passed_org={editable} passed_org={editable}
opened_from="OrgDetail" opened_from="OrgDetail"
bind:import_modal_open /> bind:import_modal_open
/>
<ConfirmOrgDeletion bind:modal_open bind:delete_org /> <ConfirmOrgDeletion bind:modal_open bind:delete_org />
{#if data_loaded} {#if data_loaded}
<section class="container p-5"> <section class="container p-5">
@@ -176,29 +177,35 @@
<span data-id="org_actions_${editable.id}"> <span data-id="org_actions_${editable.id}">
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_orgs /> bind:generate_orgs
/>
<GenerateRunnerCards bind:cards_show bind:generate_orgs /> <GenerateRunnerCards bind:cards_show bind:generate_orgs />
<GenerateRunnerCertificates bind:certificates_show bind:generate_orgs /> <GenerateRunnerCertificates bind:certificates_show bind:generate_orgs />
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:IMPORT")}
<button <button
on:click={() => { on:click={() => {
import_modal_open = true; import_modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('import-runners')} >
{$_("import-runners")}
</button> </button>
{/if} {/if}
{#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={deleteOrganization} on:click={deleteOrganization}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>{$_("confirm-delete")}</button
>
<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}
{#if !delete_triggered} {#if !delete_triggered}
<button <button
@@ -206,7 +213,9 @@
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-organization')}</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-organization")}</button
>
{/if} {/if}
{/if} {/if}
{#if !delete_triggered} {#if !delete_triggered}
@@ -215,7 +224,9 @@
disabled={!save_enabled} disabled={!save_enabled}
class:opacity-50={!save_enabled} class:opacity-50={!save_enabled}
type="button" type="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> 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>
@@ -234,12 +245,13 @@
class="h-3 w-3 stroke-current" class="h-3 w-3 stroke-current"
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg"><path xmlns="http://www.w3.org/2000/svg"
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> ><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" /></svg> <polyline points="9 22 9 12 15 12 15 22" /></svg
>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="/">{$_('home')}</a><svg <a class="mr-2" href="/">{$_("home")}</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -249,24 +261,25 @@
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="mr-2 flex items-center"> <li class="mr-2 flex items-center">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M21 20h2v2H1v-2h2V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17zm-2 0V4H5v16h14zM8 11h3v2H8v-2zm0-4h3v2H8V7zm0 8h3v2H8v-2zm5 0h3v2h-3v-2zm0-4h3v2h-3v-2zm0-4h3v2h-3V7z" /></svg> d="M21 20h2v2H1v-2h2V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17zm-2 0V4H5v16h14zM8 11h3v2H8v-2zm0-4h3v2H8V7zm0 8h3v2H8v-2zm5 0h3v2h-3v-2zm0-4h3v2h-3v-2zm0-4h3v2h-3V7z"
/></svg
>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<a class="mr-2" href="./">{$_('organizations')}</a><svg <a class="mr-2" href="./">{$_("organizations")}</a><svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
stroke-width="2" stroke-width="2"
@@ -276,12 +289,10 @@
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">Org-Details #{params.orgid}</span> <span class="mr-2">Org-Details #{params.orgid}</span>
@@ -291,81 +302,88 @@
</div> </div>
</div> </div>
<div class="text-sm w-full"> <div class="text-sm w-full">
<label for="name" class="font-medium text-gray-700">{$_('name')}</label> <label for="name" class="font-medium text-gray-700">{$_("name")}</label>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('name')} placeholder={$_("name")}
type="text" type="text"
bind:value={editable.name} bind:value={editable.name}
name="name" name="name"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> 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="contact" class="font-medium text-gray-700"
for="contact" >{$_("contact")}</label
class="font-medium text-gray-700">{$_('contact')}</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) => label itemFilter={(label, filterText, option) =>
.toLowerCase() label.toLowerCase().includes(filterText.toLowerCase()) ||
.includes( option.value.id.toString().startsWith(filterText.toLowerCase())}
filterText.toLowerCase()
) || option.value.id
.toString()
.startsWith(filterText.toLowerCase())}
items={contacts} items={contacts}
showChevron={true} showChevron={true}
placeholder={$_('no-contact-selected')} placeholder={$_("no-contact-selected")}
noOptionsMessage={$_('no-contact-found')} noOptionsMessage={$_("no-contact-found")}
bind:selectedValue={contact} bind:selectedValue={contact}
on:select={(selectedValue) => (editable.contact = selectedValue.detail.value)} on:select={(selectedValue) =>
on:clear={() => (editable.contact = null)} /> (editable.contact = selectedValue.detail.value)}
on:clear={() => (editable.contact = null)}
/>
</div> </div>
<div> <div>
<div class="flex items-start mt-2"> <div class="flex items-start mt-2">
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input <input
bind:checked={editable.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>
<div> <div>
{#if editable.registrationEnabled} {#if editable.registrationEnabled}
<div class="text-sm w-full"> <div class="text-sm w-full">
<div on:click={copy} class="inline-flex w-full"> <button on:click={copy} class="inline-flex w-full">
<p <p
name="token" name="token"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
>
{#if editable.registrationKey} {#if editable.registrationKey}
{registrationLink} {registrationLink}
{:else} {:else}
{$_('you-have-to-save-your-changes-to-generate-a-link')} {$_("you-have-to-save-your-changes-to-generate-a-link")}
{/if} {/if}
</p> </p>
<div <div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"> class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
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="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg> d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z"
/></svg
>
</div> </div>
</div> </button>
{#if editable.registrationKey} {#if editable.registrationKey}
<p class="text-gray-500 text-xs"> <p class="text-gray-500 text-xs">
{$_('click-to-copy-the-link-into-your-clipboard')} {$_("click-to-copy-the-link-into-your-clipboard")}
</p> </p>
{/if} {/if}
</div> </div>
{/if} {/if}
@@ -375,15 +393,17 @@
<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>
</div> </div>
@@ -391,7 +411,9 @@
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="address1" for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label> class="block text-sm font-medium text-gray-700"
>{$_("address")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder="Address" placeholder="Address"
@@ -401,65 +423,74 @@
bind:value={editable.address.address1} bind:value={editable.address.address1}
type="text" type="text"
name="address1" name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !isAddress1Valid} {#if !isAddress1Valid}
<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"
{$_('address-is-required')} >
{$_("address-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="address2" for="address2"
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label> class="block text-sm font-medium text-gray-700"
>{$_("apartment-suite-etc")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('apartment-suite-etc')} placeholder={$_("apartment-suite-etc")}
bind:value={editable.address.address2} bind:value={editable.address.address2}
type="text" type="text"
name="address2" name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> 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="col-span-6"> <div class="col-span-6">
<label <label for="zipcode" class="block text-sm font-medium text-gray-700"
for="zipcode" >{$_("zip-postal-code")}</label
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('zip-postal-code')} placeholder={$_("zip-postal-code")}
class:border-red-500={!iszipcodevalid} class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid} class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid} class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode} bind:value={editable.address.postalcode}
type="text" type="text"
name="zipcode" name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iszipcodevalid} {#if !iszipcodevalid}
<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-zipcode-postal-code-is-required')} >
{$_("valid-zipcode-postal-code-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label for="city" class="block text-sm font-medium text-gray-700"
for="city" >{$_("city")}</label
class="block text-sm font-medium text-gray-700">{$_('city')}</label> >
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('city')} placeholder={$_("city")}
class:border-red-500={!iscityvalid} class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid} class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid} class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city} bind:value={editable.address.city}
type="text" type="text"
name="city" name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" /> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
/>
{#if !iscityvalid} {#if !iscityvalid}
<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-city-is-required')} >
{$_("valid-city-is-required")}
</span> </span>
{/if} {/if}
</div> </div>
@@ -469,7 +500,7 @@
</section> </section>
{:else} {:else}
{#await promise} {#await promise}
{$_('organization-detail-is-being-loaded')} {$_("organization-detail-is-being-loaded")}
{:catch error} {:catch error}
<PromiseError /> <PromiseError />
{/await} {/await}

View File

@@ -51,7 +51,7 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="mb-4" />
<div class="h-12"> <div class="h-12">
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
@@ -132,7 +132,7 @@
<div class="text-sm font-medium text-gray-900"> <div class="text-sm font-medium text-gray-900">
{#if o.address.address1 !== null} {#if o.address.address1 !== null}
{o.address.address1}<br /> {o.address.address1}<br />
{o.address.address2 || ''}<br /> <!-- {o.address.address2 || ''}<br /> -->
{o.address.postalcode} {o.address.postalcode}
{o.address.city} {o.address.city}
{o.address.country} {o.address.country}

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,30 +0,0 @@
<script>
import { _, json } from "svelte-i18n";
import { getlang } from "./datatable_i18n";
import { Grid } from "gridjs";
//
let table;
const datatable = new Grid({
columns: ["Name", "Email", "Phone Number"],
language: getlang($json("datatable")),
sort: true,
search: { enabled: true },
data: [
["John", "john@example.com", "(353) 01 222 3333"],
["Mark", "mark@gmail.com", "(01) 22 888 4444"],
["Eoin", "eoin@gmail.com", "0097 22 654 00033"],
["Sarah", "sarahcdd@gmail.com", "+322 876 1233"],
["Afshin", "afshin@mail.com", "(353) 22 87 8356"],
],
pagination: {
enabled: true,
limit: 2,
summary: false,
},
});
setTimeout(() => {
datatable.render(table);
}, 0);
</script>
<div bind:this={table} />

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,
@@ -11,8 +11,10 @@
import isMobilePhone from "validator/es/lib/isMobilePhone"; import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import Select from "svelte-select"; import Select from "svelte-select";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let modal_open; export let modal_open;
export let current_runners;
$: selected_team = undefined; $: selected_team = undefined;
let firstname_input; let firstname_input;
let lastname_input; let lastname_input;
@@ -107,8 +109,7 @@
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_runners.push(result); dispatch("created", { runners: [result] });
current_runners = current_runners;
}) })
.catch((err) => { .catch((err) => {
// //
@@ -125,7 +126,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

@@ -0,0 +1,115 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_runner = {
id: 0,
firstname: "",
lastname: "",
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_runner.id });
modal_open=false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<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
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("confirm-delete")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_("please-confirm-the-deletion-of-runner")}
</p>
</div>
<div class="w-full">
<span class="inline-block"
>{delete_runner.firstname} {delete_runner.lastname}</span
>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("delete")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -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,
@@ -16,7 +16,6 @@
export let passed_org; export let passed_org;
export let passed_orgs; export let passed_orgs;
export let passed_team; export let passed_team;
export let current_runners;
export let import_modal_open; export let import_modal_open;
$: searchvalue = ""; $: searchvalue = "";
$: importButtonEnabled = $: importButtonEnabled =
@@ -169,7 +168,7 @@
mapped mapped
) )
.then((resp) => { .then((resp) => {
current_runners = current_runners.concat(resp); dispatch("created", { runners: resp });
toast.hideToast(); toast.hideToast();
recent_processed = true; recent_processed = true;
Toastify({ Toastify({
@@ -198,7 +197,7 @@
mapped mapped
) )
.then((resp) => { .then((resp) => {
current_runners = current_runners.concat(resp); dispatch("created", { runners: resp });
toast.hideToast(); toast.hideToast();
recent_processed = true; recent_processed = true;
Toastify({ Toastify({
@@ -228,7 +227,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

@@ -7,35 +7,43 @@
$: current_runners = []; $: current_runners = [];
export let modal_open = false; export let modal_open = false;
export let import_modal_open = false; export let import_modal_open = false;
let addRunners;
</script> </script>
<section class="container p-5"> <section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight"> <span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('runners')} {$_("runners")}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<button <button
on:click={() => { on:click={() => {
modal_open = true; modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('laeufer-hinzufuegen')} >
{$_("laeufer-hinzufuegen")}
</button> </button>
<button <button
on:click={() => { on:click={() => {
import_modal_open = true; import_modal_open = true;
}} }}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('import-runners')} >
{$_("import-runners")}
</button> </button>
{/if} {/if}
</span> </span>
<RunnersOverview bind:current_runners /> <RunnersOverview bind:current_runners bind:addRunners />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")}
<AddRunnerModal bind:current_runners bind:modal_open /> <AddRunnerModal
bind:modal_open
on:created={(event) => {
addRunners(event.detail.runners);
}}
/>
<ImportRunnerModal <ImportRunnerModal
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
import_modal_open = false; import_modal_open = false;
@@ -43,7 +51,10 @@
passed_team={{}} passed_team={{}}
passed_orgs={[]} passed_orgs={[]}
passed_org={{}} passed_org={{}}
bind:current_runners
opened_from="RunnerOverview" opened_from="RunnerOverview"
bind:import_modal_open /> bind:import_modal_open
on:created={(event) => {
addRunners(event.detail.runners);
}}
/>
{/if} {/if}

View File

@@ -1,263 +1,267 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n";
import { import {
RunnerOrganizationService,
RunnerService, RunnerService,
RunnerTeamService, RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
import { writable } from "svelte/store";
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 GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte"; import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
$: searchvalue = ""; import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
$: active_deletes = []; import InputElement from "../shared/InputElement.svelte";
import TableActions from "../shared/TableActions.svelte";
import { groupFilter } from "../shared/tablefilters";
import DeleteRunnerModal from "./DeleteRunnerModal.svelte";
import Toastify from "toastify-js";
import TableBottom from "../shared/TableBottom.svelte";
import TableHeader from "../shared/TableHeader.svelte";
$: selectedRunners =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined;
let dataLoaded = false;
export let current_runners = []; export let current_runners = [];
const runners_promise = RunnerService.runnerControllerGetAll().then((val) => { $: sponsoring_contracts_show = selected.length > 0;
current_runners = val; $: cards_show = selected.length > 0;
}); $: certificates_show = selected.length > 0;
$: 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) {
return { value: g.id, label: g.parentGroup.name + " > " + g.name };
});
$: selectgroups = orgs
.map(function (g) {
return { value: g.id, label: g.name };
})
.concat(mappedteams);
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { export const addRunners = (runners) => {
teams = val; current_runners = current_runners.concat(...runners);
options.update((options) => ({
...options,
data: current_runners,
}));
};
//Section table
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "firstname",
header: () => $_("first-name"),
filterFn: `includesString`,
},
{
accessorKey: "middlename",
header: () => $_("middle-name"),
cell: (info) => {
if (!info || !info.getValue()) {
return "";
}
return info.getValue();
},
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: `group`,
},
{
accessorKey: "distance",
header: () => $_("distance"),
cell: (info) => {
if (info.getValue() < 1000) {
return `${info.getValue()} m`;
}
return `${(info.getValue() / 1000).toFixed(1)} km`;
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_delete =
current_runners[
current_runners.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"RUNNER:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
filterFns: {
group: groupFilter,
},
initialState: {
pagination: {
pageSize: 50,
},
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
}); });
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => { const table = createSvelteTable(options);
orgs = val;
}); async function deleteRunner(delete_runner_id) {
function should_display_based_on_id(id) { await RunnerService.runnerControllerRemove(delete_runner_id, true);
if (searchvalue.toString().slice(-1) === "*") { current_runners = current_runners.filter((r) => r.id !== delete_runner_id);
return id.toString().startsWith(searchvalue.replace("*", "")); options.update((options) => ({
} ...options,
return id.toString() === searchvalue; data: current_runners,
}));
Toastify({
text: $_("runner-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
} }
onMount(async () => {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
orgs = val;
}
);
let page = 0;
while (page >= 0) {
const runners = await RunnerService.runnerControllerGetAll(page, 1000);
if (runners.length == 0) {
page = -2;
}
current_runners = current_runners.concat(...runners);
options.update((options) => ({
...options,
data: current_runners,
}));
dataLoaded = true;
page++;
}
console.log("All runners loaded");
});
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')} <DeleteRunnerModal
{#await runners_promise} delete_runner={active_delete}
modal_open={active_delete != undefined}
on:delete={(event) => {
deleteRunner(event.detail.id);
}}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")}
{#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 mt-2">
<RunnersEmptyState /> <GenerateSponsoringContracts
{:else} bind:sponsoring_contracts_show
<input bind:generate_runners={selectedRunners}
type="search" />
bind:value={searchvalue} <GenerateRunnerCards
placeholder={$_('datatable.search')} bind:cards_show
aria-label={$_('datatable.search')} bind:generate_runners={selectedRunners}
class="gridjs-input gridjs-search-input mb-4" /> />
<div class="block mb-6"> <GenerateRunnerCertificates
<label bind:certificates_show
for="country" bind:generate_runners={selectedRunners}
class="text-sm font-medium text-gray-700">{$_('filter-by-organization-team')}</label> />
<Select </div>
on:select={(event) => { <div class="overflow-x-auto">
selectedFilter = event.detail; <table class="w-full">
}} <thead>
selectedValue={selectedFilter} {#each $table.getHeaderGroups() as headerGroup}
placeholder={$_('filter-by-organization-team')} <tr class="select-none">
containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
items={selectgroups} <InputElement
isMulti={true} /> type="checkbox"
</div> checked={$table.getIsAllRowsSelected()}
<div class="h-12"> indeterminate={$table.getIsSomeRowsSelected()}
<GenerateSponsoringContracts on:change={() => $table.toggleAllRowsSelected()}
bind:sponsoring_contracts_show />
bind:generate_runners />
<GenerateRunnerCards
bind:cards_show
bind:generate_runners />
<GenerateRunnerCertificates
bind:certificates_show
bind:generate_runners />
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span
on:click={() => {
const newstate = !current_runners.some((r) => r.is_selected === true);
current_runners = current_runners.map((r) => {
r.is_selected = newstate;
return r;
});
}}
class="underline cursor-pointer select-none">{#if current_runners.some((r) => r.is_selected === true)}
{$_('deselect-all')}
{:else}{$_('select-all')}{/if}
</span>
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact-information')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('group')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('distance-in-km')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th> </th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr> </tr>
</thead> {/each}
<tbody class="divide-y divide-gray-200"> </thead>
{#each current_runners as runner} <tbody>
{#if runner.firstname {#each $table.getRowModel().rows as row}
.toLowerCase() <tr>
.includes( <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
searchvalue.toLowerCase() <InputElement
) || runner.lastname type="checkbox"
.toLowerCase() checked={row.getIsSelected()}
.includes( on:change={() => row.toggleSelected()}
searchvalue.toLowerCase() />
) || should_display_based_on_id(runner.id)} </td>
{#if filterGroupIDs.includes(runner.group.id) || filterGroupIDs.includes(runner.group.parentGroup?.id) || filterGroupIDs.length === 0} {#each row.getVisibleCells() as cell}
<tr <td>
data-rowid="user_{runner.id}" <svelte:component
data-groupid={runner.group.id}> this={flexRender(
<td class="px-6 py-4 whitespace-nowrap"> cell.column.columnDef.cell,
<input cell.getContext()
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> {/each}
<td class="px-6 py-4 whitespace-nowrap"> </tr>
<div class="flex items-center"> {/each}
<div class="ml-4"> </tbody>
<div class="text-sm font-medium text-gray-900"> </table>
{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}
</tr>
{/if}
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div> </div>
{/await} <div class="h-2" />
{/if}
{/if} {/if}
<TableBottom {table} {selected} />

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,23 +1,24 @@
<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,
} from "@odit/lfk-client-js"; } from "@odit/lfk-client-js";
import Select from "svelte-select"; import Select from "svelte-select";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
export let current_scans;
const getRunnerLabel = (option) => const getRunnerLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterRunners = (label, filterText, option) => const filterRunners = (label, filterText, option) =>
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
option.value.toString().startsWith(filterText.toLowerCase()); option.value.id.toString().startsWith(filterText.toLowerCase());
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
const dispatch = createEventDispatcher();
$: runner = 0; $: runner = 0;
$: runners = []; $: runners = [];
RunnerService.runnerControllerGetAll().then((val) => { RunnerService.runnerControllerGetAll().then((val) => {
@@ -63,8 +64,7 @@
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_scans.push(result); dispatch("created", { scans: [result] });
current_scans = current_scans;
}) })
.catch((err) => { .catch((err) => {
// //
@@ -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

@@ -0,0 +1,110 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_scan = {
id: 0,
runner: {
firstname: "",
lastname: "",
},
};
const dispatch = createEventDispatcher();
onMount(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
});
async function submit() {
dispatch("delete", { id: delete_scan.id });
modal_open = false;
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<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
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("confirm-delete")}
</h3>
<div class="mt-2 mb-6">
{$_("please-confirm-the-deletion-of-scan")} #{delete_scan.id}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("delete")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
export let valid = false;
</script>
{#if valid}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("valid")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("invalid")}</span
>
{/if}

View File

@@ -5,6 +5,7 @@
import ScansOverview from "./ScansOverview.svelte"; import ScansOverview from "./ScansOverview.svelte";
$: current_scans = []; $: current_scans = [];
export let modal_open = false; export let modal_open = false;
let addScans;
</script> </script>
<section class="container p-5"> <section class="container p-5">
@@ -21,9 +22,11 @@
</button> </button>
{/if} {/if}
</span> </span>
<ScansOverview bind:current_scans /> <ScansOverview bind:current_scans bind:addScans />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:CREATE')}
<AddScanModal bind:current_scans bind:modal_open /> <AddScanModal bind:modal_open on:created={(event)=>{
addScans(event.detail.scans)
}} />
{/if} {/if}

View File

@@ -1,197 +1,308 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { import { ScanService, TrackService } from "@odit/lfk-client-js";
ScanService,
} from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { onMount } from "svelte";
import { writable } from "svelte/store";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import TableBottom from "../shared/TableBottom.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import ScansEmptyState from "./ScansEmptyState.svelte"; import ScansEmptyState from "./ScansEmptyState.svelte";
$: searchvalue = ""; import InputElement from "../shared/InputElement.svelte";
$: active_deletes = []; import TableActions from "../shared/TableActions.svelte";
import { runnerFilter, statusFilter } from "../shared/tablefilters";
import CardRunner from "../cards/CardRunner.svelte";
import ScanValid from "./ScanValid.svelte";
import DeleteScanModal from "./DeleteScanModal.svelte";
$: selectedScans =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined;
$: dataLoaded = false;
export let current_scans = []; export let current_scans = [];
const scans_promise = ScanService.scanControllerGetAll().then((val) => { export const addScans = (scans) => {
current_scans = val; current_scans = current_scans.concat(...scans);
options.update((options) => ({
...options,
data: current_scans,
}));
};
let allTracks = [];
TrackService.trackControllerGetAll().then((val) => {
allTracks = val;
}); });
function should_display_based_on_id(id) { function format_laptime(laptime) {
if (searchvalue.toString().slice(-1) === "*") { if (laptime == 0 || laptime == null) {
return id.toString().startsWith(searchvalue.replace("*", "")); return $_("first-scan-of-the-day");
} }
return id.toString() === searchvalue; if (laptime < 60) {
return `${laptime}s`;
}
if (laptime < 3600) {
return `${Math.floor(laptime / 60)}min ${
laptime - 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
}`;
} }
function format_laptime(laptime){
if(laptime == 0 || laptime == null){return $_('first-scan-of-the-day')} const columns = [
if(laptime < 60){return `${laptime}s`} {
if(laptime < 3600){return `${Math.floor(laptime / 60)}min ${laptime - (Math.floor(laptime / 60)*60)}s`} accessorKey: "id",
return `${Math.floor(laptime / 3600)}h ${laptime - (Math.floor(laptime / 3600)*3600)}min ${laptime - (Math.floor(laptime / 3600)*3600) - (Math.floor(laptime / 60)*60)}` header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
return renderComponent(CardRunner, { runner: info.getValue() });
},
filterFn: `runner`,
},
{
accessorKey: "lapTime",
header: () => $_("laptime"),
cell: (info) => {
return format_laptime(info.getValue());
},
enableColumnFilter: false,
},
{
accessorKey: "timestamp",
header: () => $_("timestamp"),
cell: (info) => {
return new Date(parseInt(info.getValue()) * 1000).toLocaleString();
},
enableColumnFilter: false,
},
{
accessorKey: "distance",
header: () => $_("distance"),
cell: (info) => {
if (info.getValue() < 1000) {
return `${info.getValue()}m`;
}
return `${(info.getValue() / 1000).toFixed(1)}km`;
},
enableColumnFilter: false,
},
{
accessorKey: "track",
header: () => $_("track"),
cell: (info) => {
const track = info.getValue();
return track?.name || "?";
},
enableColumnFilter: true,
},
{
accessorKey: "valid",
cell: (info) => {
return renderComponent(ScanValid, { valid: info.getValue() });
},
header: () => $_("status"),
filterFn: `status`,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_delete =
current_scans[
current_scans.findIndex((r) => r.id == info.row.original.id)
];
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes("SCAN:DELETE"),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
runner: runnerFilter,
status: statusFilter,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
async function deleteScan(scan_id) {
await ScanService.scanControllerRemove(scan_id, true);
current_scans = current_scans.filter((r) => r.id !== scan_id);
options.update((options) => ({
...options,
data: current_scans,
}));
Toastify({
text: $_("scan-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
} }
onMount(async () => {
let page = 0;
while (page >= 0) {
const scans = await ScanService.scanControllerGetAll(page, 500);
if (scans.length == 0) {
page = -2;
}
current_scans = current_scans.concat(...scans);
options.update((options) => ({
...options,
data: current_scans,
}));
dataLoaded = true;
page++;
}
console.log("All scans loaded");
});
</script> </script>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:GET')} <DeleteScanModal
{#await scans_promise} delete_scan={active_delete}
modal_open={active_delete != undefined}
on:delete={(event) => {
deleteScan(event.detail.id);
}}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
{#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">{$_('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} {:else if current_scans.length === 0}
{#if current_scans.length === 0} <ScansEmptyState />
<ScansEmptyState /> {:else}
{:else} {#if selected.length > 0}
<input <button
type="search" type="button"
bind:value={searchvalue} 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"
placeholder={$_('datatable.search')} id="options-menu"
aria-label={$_('datatable.search')} on:click={async () => {
class="gridjs-input gridjs-search-input mb-4" /> const prom = [];
<div for (const scan of selectedScans) {
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"> prom.push(ScanService.scanControllerRemove(scan.id, true));
<table class="divide-y divide-gray-200 w-full"> }
<thead class="bg-gray-50"> await Promise.all(prom);
<tr> for (const scan of selectedScans) {
<th current_scans = current_scans.filter((r) => r.id !== scan.id);
scope="col" }
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> options.update((options) => ({
{$_('runner')} ...options,
</th> data: current_scans,
<th }));
scope="col" $table.resetRowSelection();
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Toastify({
{$_('distance-track')} text: $_("scan-deleted"),
</th> duration: 3500,
<th backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
scope="col" }).showToast();
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> }}
{$_('laptime')} >
</th> {$_("delete-scans")}
<th <svg
scope="col" xmlns="http://www.w3.org/2000/svg"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> fill="none"
{$_('status')} viewBox="0 0 24 24"
</th> stroke-width="1.5"
<th scope="col" class="relative px-6 py-3"> stroke="currentColor"
<span class="sr-only">{$_('action')}</span> class="w-5 h-5"
</th> >
</tr> <path
</thead> stroke-linecap="round"
<tbody class="divide-y divide-gray-200"> stroke-linejoin="round"
{#each current_scans as scan} 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"
{#if scan.track?.name />
.toLowerCase() </svg>
.includes( </button>
searchvalue.toLowerCase()
) || scan.runner?.firstname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || scan.runner?.lastname
.toLowerCase()
.includes(
searchvalue.toLowerCase()
) || should_display_based_on_id(scan.id)}
<tr data-rowid="scan_{scan.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<a
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}
{scan.runner.middlename || ''}
{scan.runner.lastname}</a>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
{#if scan.distance < 1000}
{scan.distance}m
{:else}{scan.distance / 1000}km{/if}
{#if scan.track}
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{scan.track.name}
</a>
{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if scan.responseType === "TRACKSCAN"}
<div class="text-sm font-medium text-gray-900">
{format_laptime(scan.lapTime)}
</div>
{:else}
<div class="text-sm font-medium text-gray-900">
{$_('scan-with-fixed-distance')}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if scan.valid}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">{$_('valid')}</span>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{$_('invalid')}</span>
{/if}
</div>
</td>
{#if active_deletes[scan.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[scan.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
ScanService.scanControllerRemove(scan.id, false).then(
(resp) => {
current_scans = current_scans.filter(
(obj) => obj.id !== scan.id
);
Toastify({
text: 'Scan deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{scan.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('SCAN:DELETE')}
<button
on:click={() => {
active_deletes[scan.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if} {/if}
{:catch error} <div class="overflow-x-auto">
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> <table class="w-full">
<span class="inline-block align-middle mr-8"> <thead>
<b class="capitalize">{$_('general_promise_error')}</b> {#each $table.getHeaderGroups() as headerGroup}
{error} <tr class="select-none">
</span> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<InputElement
type="checkbox"
checked={$table.getIsAllRowsSelected()}
indeterminate={$table.getIsSomeRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()}
/>
</th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr>
<td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<InputElement
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> </div>
{/await} <TableBottom {table} {selected} />
{/if}
{/if} {/if}

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";
@@ -13,7 +13,7 @@
dispatch("cancelDelete", { id: delete_station.id }); dispatch("cancelDelete", { id: delete_station.id });
} }
function deleteStation() { function deleteStation() {
ScanStationService.donorControllerRemove( ScanStationService.scanStationControllerRemove(
delete_station.id, delete_station.id,
true true
) )
@@ -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,14 +1,18 @@
<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";
import bwipjs from "bwip-js";
export let copy_modal_open; export let copy_modal_open;
export let new_station; export let new_station;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let valueCopy = null; let valueCopy = null;
let areaDom; let areaDom;
let copied = false; let copied = false;
$: is_qrcode = false;
$: barcode = textToBase64Barcode(new_station.key, is_qrcode);
function close() { function close() {
copy_modal_open = false; copy_modal_open = false;
} }
@@ -36,91 +40,167 @@
"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();
} }
// we can notifi by event or storage about copy status // we can notifi by event or storage about copy status
valueCopy = null; valueCopy = null;
} }
function textToBase64Barcode(text, is_qrcode) {
const canvas = document.createElement("canvas");
let bcid = "code128";
if (is_qrcode) {
bcid = "qrcode";
}
let codeconfig = {
bcid,
text: `${text}`,
scale: 4,
includetext: true,
textxalign: "center",
backgroundcolor: "ffffff",
};
if (bcid == "code128") {
codeconfig.height = 10;
}
bwipjs.toCanvas(canvas, codeconfig);
return canvas.toDataURL("image/png");
}
</script> </script>
{#if copy_modal_open} {#if copy_modal_open}
{#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">
<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 overflow-hidden 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 overflow-hidden 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"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
/></svg
>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('token')} {$_("token")}
</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">
{$_('the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again')} {$_(
"the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again"
)}
<br /> <br />
{$_('please-copy-the-token-and-store-it-somewhere-save')} {$_("please-copy-the-token-and-store-it-somewhere-save")}
</p> </p>
</div> </div>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<label <label
for="token" for="token"
class="block text-sm font-medium text-gray-700">{$_('token')}</label> class="block text-sm font-medium text-gray-700"
<div on:click={copy} class="inline-flex"> >{$_("token")}</label
>
<button on:click={copy} class="inline-flex">
<p <p
name="token" name="token"
class:bg-green-200={copied} class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
>
{new_station.key} {new_station.key}
</p> </p>
<div <div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"> class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
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="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg> d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z"
/></svg
>
</div> </div>
</div> </button>
<p class="text-gray-500 text-xs"> <p class="text-gray-500 text-xs">
{$_('click-to-copy-token-to-clipboard')} {$_("click-to-copy-token-to-clipboard")}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div class="mx-auto text-center items-center">
<h2 class="text-lg leading-6 font-medium text-gray-900">
{$_("config-codes")}
</h2>
<span class="flex items-center text-center">
<p class="text-md text-gray-900 mr-3">Format:</p>
<label for="codeswitch" class="text-md text-gray-900 mr-3"
>Code128</label
>
<input
id="codeswitch"
type="checkbox"
bind:checked={is_qrcode}
class="relative shrink-0 w-[3.25rem] h-7 bg-gray-100 checked:bg-none checked:bg-blue-600 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 border border-transparent ring-1 ring-transparent focus:border-blue-600 focus:ring-blue-600 ring-offset-white focus:outline-none appearance-none before:inline-block before:w-6 before:h-6 before:bg-white checked:before:bg-blue-200 before:translate-x-0 checked:before:translate-x-full before:shadow before:rounded-full before:transform before:ring-0 before:transition before:ease-in-out before:duration-200 dark:before:bg-gray-400 dark:checked:before:bg-blue-200"
/>
<label for="codeswitch" class="text-md text-gray-900 ml-3"
>QR-Code</label
>
</span>
<h3 class="leading-6 font-medium text-gray-900">
{$_("api-endpoint")}
</h3>
<img
class:w-[50%]={is_qrcode}
class:w-full={!is_qrcode}
class="md:w-auto mb-2 mx-auto"
alt="Registrierungscode"
src={textToBase64Barcode(config.baseurl, is_qrcode)}
/>
<h3 class="leading-6 font-medium text-gray-900">{$_("token")}</h3>
<img
class:w-[50%]={is_qrcode}
class:w-full={!is_qrcode}
class="md:w-auto mb-2 mx-auto"
alt="Registrierungscode"
src={barcode}
/>
</div>
</div> </div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button <button
on:click={close} on:click={close}
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-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('yes-i-copied-the-token')} >
{$_("yes-i-copied-the-token")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -41,7 +41,7 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="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"> <table class="divide-y divide-gray-200 w-full">

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

@@ -0,0 +1,26 @@
<script>
import { _ } from "svelte-i18n";
export let detailsLink;
export let detailsAction;
export let deleteEnabled;
export let deleteAction;
</script>
{#if detailsLink}
<a href={detailsLink} class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{:else if detailsAction}
<button on:click={detailsAction} class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</button
>
{/if}
{#if deleteEnabled}
<button
tabindex="0"
on:click={deleteAction}
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}

View File

@@ -0,0 +1,71 @@
<script>
export let table;
export let selected;
</script>
<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 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:input={(e) => {
const ps = Number(e.target.value);
console.log({ ps });
$table.setPageSize(Number(e.target.value));
}}
>
{#each [25, 50, 100, 250, 500] as pageSize}
<option value={pageSize}>{pageSize}</option>
{/each}
</select>
</div>
<!-- <pre>{JSON.stringify($table.getState(), null, 2)}</pre> -->
<div>
{Object.keys(selected).length} of{" "}
{$table.getPreFilteredRowModel().rows.length} Total Rows Selected
</div>

View File

@@ -0,0 +1,57 @@
<script>
import { flexRender } from "@tanstack/svelte-table";
export let header;
</script>
<th class="cursor-pointer min-w-[5rem]">
{#if !header.isPlaceholder}
<button
class="w-full"
tabindex="0"
on:click={header.column.getToggleSortingHandler()}
>
<svelte:component
this={flexRender(header.column.columnDef.header, header.getContext())}
/>
{#if header.column
.getIsSorted()
.toString() == "asc" && header.column.getCanSort()}
🔼
{:else if header.column
.getIsSorted()
.toString() == "desc" && header.column.getCanSort()}
🔽
{/if}
</button>
{/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 w-full 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>

View File

@@ -0,0 +1,34 @@
export const groupFilter = (row, columnId, value) => {
const group = row.getValue(columnId);
if (group.responseType === "RUNNERORGANIZATION") {
return group.name.toLowerCase().includes(value.toLowerCase());
} else if (value.includes(">")) {
return (
`${group.parentGroup.name} > ${group.name}`.toLowerCase().includes(value.toLowerCase())
);
} else {
return (
group.name.toLowerCase().includes(value.toLowerCase()) ||
group.parentGroup.name.toLowerCase().includes(value.toLowerCase())
);
}
};
export const runnerFilter = (row, columnId, value) => {
const runner = row.getValue(columnId);
if(!runner && value == "blanko"){return true}
if(!runner){return false}
if(value.startsWith("#")){
return runner.id == value.replace("#","")
}
if(runner.middlename){
return `${runner.firstname} ${runner.middlename} ${runner.lastname}`.toLowerCase().includes(value.toLowerCase())
}
return `${runner.firstname} ${runner.lastname}`.toLowerCase().includes(value.toLowerCase())
};
export const statusFilter = (row, columnId, value) => {
const status = row.getValue(columnId);
return status.toString().includes(value);
};

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,8 +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 { ScanStationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
@@ -13,10 +12,7 @@
dispatch("cancelDelete", { id: delete_station.id }); dispatch("cancelDelete", { id: delete_station.id });
} }
function deleteClient() { function deleteClient() {
ScanStationService.donorControllerRemove( StatsClientService.statsClientControllerRemove(delete_station.id, true)
delete_station.id,
true
)
.then((resp) => { .then((resp) => {
Toastify({ Toastify({
text: $_('statsclient-deleted'), text: $_('statsclient-deleted'),
@@ -32,7 +28,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,10 +1,10 @@
<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;
export let new_station; export let new_client;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let valueCopy = null; let valueCopy = null;
let areaDom; let areaDom;
@@ -13,7 +13,7 @@
copy_modal_open = false; copy_modal_open = false;
} }
async function copy() { async function copy() {
valueCopy = new_station.key; valueCopy = new_client.key;
await tick(); await tick();
areaDom.focus(); areaDom.focus();
areaDom.select(); areaDom.select();
@@ -46,70 +46,87 @@
{#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">
<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 overflow-hidden 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 overflow-hidden 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"><path fill="none" d="M0 0h24v24H0z" /> viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z" /></svg> d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
/></svg
>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('token')} {$_("token")}
</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">
{$_('the-scanstations-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-wont-be-able-to-change-or-view-it-again"
)}
<br /> <br />
{$_('please-copy-the-token-and-store-it-somewhere-save')} {$_("please-copy-the-token-and-store-it-somewhere-save")}
</p> </p>
</div> </div>
<div class="mt-2 mb-6"> <div class="mt-2 mb-6">
<label <label
for="token" for="token"
class="block text-sm font-medium text-gray-700">{$_('token')}</label> class="block text-sm font-medium text-gray-700"
<div on:click={copy} class="inline-flex"> >{$_("token")}</label
>
<button on:click={copy} class="inline-flex">
<p <p
name="token" name="token"
class:bg-green-200={copied} class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"> class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
{new_station.key} >
{new_client.key}
</p> </p>
<div <div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"> class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
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="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z" /></svg> d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z"
/></svg
>
</div> </div>
</div> </button>
<p class="text-gray-500 text-xs"> <p class="text-gray-500 text-xs">
{$_('click-to-copy-token-to-clipboard')} {$_("click-to-copy-token-to-clipboard")}
</p> </p>
</div> </div>
</div> </div>
@@ -119,8 +136,9 @@
<button <button
on:click={close} on:click={close}
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-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('yes-i-copied-the-token')} >
{$_("yes-i-copied-the-token")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -80,7 +80,7 @@
</div> </div>
<div class="mb-8 text-3xl font-extrabold leading-tight"> <div class="mb-8 text-3xl font-extrabold leading-tight">
#{original_data.id} #{original_data.id}
<span data-id="stations_actions_${editable.id}"> <span data-id="stations_actions_${original_data.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:DELETE')} {#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:DELETE')}
{#if delete_triggered} {#if delete_triggered}
<button <button
@@ -98,7 +98,7 @@
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-station')}</button> class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-statsclient')}</button>
{/if} {/if}
{/if} {/if}
</span> </span>

View File

@@ -2,8 +2,8 @@
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../../store"; import store from "../../store";
import AddStatsClientModal from "./AddStatsClientModal.svelte"; import AddStatsClientModal from "./AddStatsClientModal.svelte";
// import CopyStatsClientsTokenModal from "./CopyStatsClientsTokenModal.svelte"; import CopyStatsClientTokenModal from "./CopyStatsClientTokenModal.svelte";
import StatsClientssOverview from "./StatsClientsOverview.svelte"; import StatsClientsOverview from "./StatsClientsOverview.svelte";
export let modal_open = false; export let modal_open = false;
export let copy_modal_open = false; export let copy_modal_open = false;
export let new_client = {}; export let new_client = {};
@@ -24,10 +24,10 @@
</button> </button>
{/if} {/if}
</span> </span>
<StatsClientssOverview bind:current_clients bind:modal_open bind:new_client bind:copy_modal_open /> <StatsClientsOverview bind:current_clients bind:modal_open bind:new_client bind:copy_modal_open />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes('STATSCLIENT:CREATE')}
<AddStatsClientModal bind:modal_open bind:current_clients bind:new_client bind:copy_modal_open/> <AddStatsClientModal bind:modal_open bind:current_clients bind:new_client bind:copy_modal_open/>
<!-- <CopyStatsClientsTokenModal bind:copy_modal_open bind:new_client /> --> <CopyStatsClientTokenModal bind:copy_modal_open bind:new_client />
{/if} {/if}

View File

@@ -1,7 +1,7 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import AddStatsClientModal from "./AddStatsClientModal.svelte"; import AddStatsClientModal from "./AddStatsClientModal.svelte";
import CopyScanStationTokenModal from "./CopyScanStationTokenModal.svelte"; import CopyScanStationTokenModal from "./CopyStatsClientTokenModal.svelte";
import scanstations_empty from "./statsclients_empty.svg"; import scanstations_empty from "./statsclients_empty.svg";
let modal_open = false; let modal_open = false;
let copy_modal_open = false; let copy_modal_open = false;

View File

@@ -41,7 +41,7 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="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"> <table class="divide-y divide-gray-200 w-full">
@@ -57,11 +57,6 @@
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('prefix')} {$_('prefix')}
</th> </th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('key')}
</th>
<th scope="col" class="relative px-6 py-3"> <th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span> <span class="sr-only">{$_('action')}</span>
</th> </th>
@@ -92,14 +87,6 @@
</div> </div>
</div> </div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{c.key}
</div>
</div>
</td>
{#if active_deletes[c.id] === true} {#if active_deletes[c.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">

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

@@ -57,7 +57,7 @@
bind:value={searchvalue} bind:value={searchvalue}
placeholder={$_('datatable.search')} placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')} aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" /> class="mb-4" />
<div class="h-12"> <div class="h-12">
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show

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

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

View File

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

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,5 +1,5 @@
const fs = require('fs'); import fs from 'fs';
const package = JSON.parse(fs.readFileSync(`./package.json`, { encoding: 'utf-8' })); const packagejson = JSON.parse(fs.readFileSync(`./package.json`, { encoding: 'utf-8' }));
const original = fs.readFileSync(`./index.html`, { encoding: 'utf-8' }); const original = fs.readFileSync(`./index.html`, { encoding: 'utf-8' });
let out = original.replace(/RELEASE_INFO-(\S)+-RELEASE_INFO/gi, 'RELEASE_INFO-' + package.version + '-RELEASE_INFO'); let out = original.replace(/RELEASE_INFO-(\S)+-RELEASE_INFO/gi, 'RELEASE_INFO-' + packagejson.version + '-RELEASE_INFO');
fs.writeFileSync(`./index.html`, out); fs.writeFileSync(`./index.html`, out);

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()
]
};
});