Compare commits

..

153 Commits

Author SHA1 Message Date
d6d88f5f60 🚀RELEASE v1.3.3
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 18:18:44 +02:00
2c208c4381 bumped lfk-client-js 2023-04-19 18:18:26 +02:00
39bc6c4945 🚀RELEASE v1.3.2
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 18:17:18 +02:00
b94e3b745f fix(donors): Shortened texts 2023-04-19 18:16:50 +02:00
6f337aeee1 feat(donations): Resolve donations via donor 2023-04-19 18:15:15 +02:00
5d48060834 fix(donors): Removed debug infos 2023-04-19 18:12:24 +02:00
c842c203e2 🚀RELEASE v1.3.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 18:00:58 +02:00
5bcfc8db75 More filtering 2023-04-19 18:00:41 +02:00
27b4dde755 feat(donors): Added name and address filtering 2023-04-19 18:00:16 +02:00
91ab199769 feat(donations): Donation table filtering 2023-04-19 17:53:24 +02:00
e75be49be4 🚀RELEASE v1.3.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 17:41:40 +02:00
505fb8cb08 feat(donations): Implemented donation deletion via confirm modal 2023-04-19 17:41:21 +02:00
e5c9265588 feat(donations): Implemented add donation payment via datatable refresh 2023-04-19 17:34:34 +02:00
02003ec80e feat(donations): Donations reactive create and load into datatable 2023-04-19 17:21:24 +02:00
133470b6f2 feat(donationsoverview): Switched donations overview to datatable 2023-04-19 17:12:04 +02:00
4a6230c439 Merge branch 'dev' of git.odit.services:lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-19 16:56:23 +02:00
fdc7d80bbf 🚀RELEASE v1.2.0 2023-04-19 16:56:09 +02:00
352551e168 feat(donorsoverview): Dynamicly add newly generated donors 2023-04-19 16:55:14 +02:00
7aec050419 feat(donorsoverview): Implemented delete confirmation with datatable 2023-04-19 16:49:54 +02:00
4289034436 new license file version [CI SKIP] 2023-04-19 14:45:02 +00:00
8f8858f100 feat(DonationsOverview): i18n loading text
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 16:44:39 +02:00
d98fb0d5b2 feat(donoroverview): Added datatable formatters 2023-04-19 16:39:30 +02:00
5014bf5bc5 feat(donors): Load donors paginated 2023-04-19 16:23:00 +02:00
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
60 changed files with 4268 additions and 2763 deletions

View File

@@ -2,9 +2,242 @@
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.3.3](https://git.odit.services/lfk/frontend/compare/1.3.2...1.3.3)
- bumped lfk-client-js [`2c208c4`](https://git.odit.services/lfk/frontend/commit/2c208c438185892270a0ebd37deb6a7c9ac08fc0)
#### [1.3.2](https://git.odit.services/lfk/frontend/compare/1.3.1...1.3.2)
> 19 April 2023
- 🚀RELEASE v1.3.2 [`39bc6c4`](https://git.odit.services/lfk/frontend/commit/39bc6c49450964510f996369d014f92c569188ae)
- fix(donors): Shortened texts [`b94e3b7`](https://git.odit.services/lfk/frontend/commit/b94e3b745f2febbe91e16a7a26f96b47d347ab92)
- feat(donations): Resolve donations via donor [`6f337ae`](https://git.odit.services/lfk/frontend/commit/6f337aeee16267d1e67e3d3855b63b6f2e57979f)
- fix(donors): Removed debug infos [`5d48060`](https://git.odit.services/lfk/frontend/commit/5d48060834717b2244172a0914e2690f8fe634d9)
#### [1.3.1](https://git.odit.services/lfk/frontend/compare/1.3.0...1.3.1)
> 19 April 2023
- feat(donations): Donation table filtering [`91ab199`](https://git.odit.services/lfk/frontend/commit/91ab199769c9f4f8051c74ad43a701db321f3995)
- feat(donors): Added name and address filtering [`27b4dde`](https://git.odit.services/lfk/frontend/commit/27b4dde7551995c9d7e8ca33a9bd97d429a35801)
- 🚀RELEASE v1.3.1 [`c842c20`](https://git.odit.services/lfk/frontend/commit/c842c203e2fbf0a201297d475db9047c0691bd52)
- More filtering [`5bcfc8d`](https://git.odit.services/lfk/frontend/commit/5bcfc8db752fce96e9f523d14cefff1a4f675661)
#### [1.3.0](https://git.odit.services/lfk/frontend/compare/1.2.0...1.3.0)
> 19 April 2023
- feat(donations): Implemented donation deletion via confirm modal [`505fb8c`](https://git.odit.services/lfk/frontend/commit/505fb8cb08b81a7dcb08561bdda0f6464f140d3e)
- 🚀RELEASE v1.3.0 [`e75be49`](https://git.odit.services/lfk/frontend/commit/e75be49be42c3d5581e2204bfa064bfa3778c1b6)
- feat(donationsoverview): Switched donations overview to datatable [`133470b`](https://git.odit.services/lfk/frontend/commit/133470b6f2a63ec087f27c98ef260648a8672e5f)
- feat(donations): Implemented add donation payment via datatable refresh [`e5c9265`](https://git.odit.services/lfk/frontend/commit/e5c92655886ad9a6fcd7565fadd7955c477c3595)
- feat(donations): Donations reactive create and load into datatable [`02003ec`](https://git.odit.services/lfk/frontend/commit/02003ec80efc16aabd126710a6eeac18df43f841)
- feat(DonationsOverview): i18n loading text [`8f8858f`](https://git.odit.services/lfk/frontend/commit/8f8858f10071ddf9988d0ec0e3c4a891db24a102)
- new license file version [CI SKIP] [`4289034`](https://git.odit.services/lfk/frontend/commit/4289034436869750205a946247e7ab5f9892fe98)
#### [1.2.0](https://git.odit.services/lfk/frontend/compare/1.1.0...1.2.0)
> 19 April 2023
- feat(donoroverview): Added datatable formatters [`d98fb0d`](https://git.odit.services/lfk/frontend/commit/d98fb0d5b288c987a45ccbf2bb026ccaab539a71)
- 🚀RELEASE v1.2.0 [`fdc7d80`](https://git.odit.services/lfk/frontend/commit/fdc7d80bbf9bd698128e9ec4f91fa813499777a9)
- feat(donors): Load donors paginated [`5014bf5`](https://git.odit.services/lfk/frontend/commit/5014bf5bc5873cfe4ae04d71b4aff12b257dd2e3)
- feat(donorsoverview): Dynamicly add newly generated donors [`352551e`](https://git.odit.services/lfk/frontend/commit/352551e168b5dced5e7353e82655908d82d28af0)
- feat(donorsoverview): Implemented delete confirmation with datatable [`7aec050`](https://git.odit.services/lfk/frontend/commit/7aec050419f6f1bf853c3e1bc655b01725ed3b65)
#### [1.1.0](https://git.odit.services/lfk/frontend/compare/1.0.0...1.1.0)
> 19 April 2023
- 🚀RELEASE v1.1.0 [`0708cab`](https://git.odit.services/lfk/frontend/commit/0708cabc75e63a876e54a0b343318f8d934ae319)
- 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 -> StatsClientService [`5291e04`](https://git.odit.services/lfk/frontend/commit/5291e049a1d2e880fbe277095da91b70d4812c3f)
- fix(ConfirmScanStationDeletion): donorControllerRemove -> 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 -> 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)
- -> 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) #### [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) - 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) - 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) #### [0.17.2](https://git.odit.services/lfk/frontend/compare/0.17.1...0.17.2)

View File

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

View File

@@ -1,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,6 +1,6 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "0.17.3", "version": "1.3.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"i18n-order": "node order.js", "i18n-order": "node order.js",
@@ -13,10 +13,8 @@
"devDependencies": { "devDependencies": {
"@odit/license-exporter": "0.0.12", "@odit/license-exporter": "0.0.12",
"@sveltejs/vite-plugin-svelte": "2.0.4", "@sveltejs/vite-plugin-svelte": "2.0.4",
"@types/html-minifier": "4.0.2",
"auto-changelog": "2.4.0", "auto-changelog": "2.4.0",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"html-minifier": "4.0.0",
"postcss": "8.4.21", "postcss": "8.4.21",
"release-it": "15.10.1", "release-it": "15.10.1",
"svelte-select": "3.17.0", "svelte-select": "3.17.0",
@@ -28,7 +26,7 @@
"commit": true, "commit": true,
"requireCleanWorkingDir": false, "requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}", "commitMessage": "🚀RELEASE v${version}",
"push": false, "push": true,
"tag": true, "tag": true,
"tagName": null, "tagName": null,
"tagAnnotation": "v${version}" "tagAnnotation": "v${version}"
@@ -41,11 +39,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@odit/lfk-client-js": "0.14.3", "@odit/lfk-client-js": "1.1.1",
"@paralleldrive/cuid2": "^2.2.0", "@paralleldrive/cuid2": "^2.2.0",
"@tanstack/svelte-table": "^8.8.5", "@tanstack/svelte-table": "^8.8.5",
"@tanstack/table-core": "^8.8.5", "bwip-js": "^3.4.0",
"@vincjo/datatables": "^1.5.2",
"check-password-strength": "2.0.7", "check-password-strength": "2.0.7",
"csvtojson": "2.0.10", "csvtojson": "2.0.10",
"gridjs": "3.4.0", "gridjs": "3.4.0",

125
pnpm-lock.yaml generated
View File

@@ -2,20 +2,17 @@ lockfileVersion: '6.0'
dependencies: dependencies:
'@odit/lfk-client-js': '@odit/lfk-client-js':
specifier: 0.14.3 specifier: 1.1.0
version: 0.14.3 version: 1.1.0
'@paralleldrive/cuid2': '@paralleldrive/cuid2':
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0 version: 2.2.0
'@tanstack/svelte-table': '@tanstack/svelte-table':
specifier: ^8.8.5 specifier: ^8.8.5
version: 8.8.5(svelte@3.58.0) version: 8.8.5(svelte@3.58.0)
'@tanstack/table-core': bwip-js:
specifier: ^8.8.5 specifier: ^3.4.0
version: 8.8.5 version: 3.4.0
'@vincjo/datatables':
specifier: ^1.5.2
version: 1.5.2
check-password-strength: check-password-strength:
specifier: 2.0.7 specifier: 2.0.7
version: 2.0.7 version: 2.0.7
@@ -57,18 +54,12 @@ devDependencies:
'@sveltejs/vite-plugin-svelte': '@sveltejs/vite-plugin-svelte':
specifier: 2.0.4 specifier: 2.0.4
version: 2.0.4(svelte@3.58.0)(vite@4.2.1) version: 2.0.4(svelte@3.58.0)(vite@4.2.1)
'@types/html-minifier':
specifier: 4.0.2
version: 4.0.2
auto-changelog: auto-changelog:
specifier: 2.4.0 specifier: 2.4.0
version: 2.4.0 version: 2.4.0
autoprefixer: autoprefixer:
specifier: 10.4.14 specifier: 10.4.14
version: 10.4.14(postcss@8.4.21) version: 10.4.14(postcss@8.4.21)
html-minifier:
specifier: 4.0.0
version: 4.0.0
postcss: postcss:
specifier: 8.4.21 specifier: 8.4.21
version: 8.4.21 version: 8.4.21
@@ -519,8 +510,8 @@ packages:
'@octokit/openapi-types': 16.0.0 '@octokit/openapi-types': 16.0.0
dev: true dev: true
/@odit/lfk-client-js@0.14.3: /@odit/lfk-client-js@1.1.0:
resolution: {integrity: sha512-oOZ9jjzqcbMA0Sfwxn4q9+8hHckMU2IhAn7v0OAS54zcnquYQANnY4RMEoNIyXd0oEe1z8QewBjyBvFEDg6BmA==} resolution: {integrity: sha512-yhjsi7YMzL9/fJ7o06yszzw15iZhao3VmX0G9oqZWFwYJd1M2td3Lvm76mXNzTVlbdG6W0W3+eEjcalBdo51Pg==}
dev: false dev: false
/@odit/license-exporter@0.0.12: /@odit/license-exporter@0.0.12:
@@ -608,43 +599,10 @@ packages:
engines: {node: '>= 6'} engines: {node: '>= 6'}
dev: true dev: true
/@types/clean-css@4.2.6:
resolution: {integrity: sha512-Ze1tf+LnGPmG6hBFMi0B4TEB0mhF7EiMM5oyjLDNPE9hxrPU0W+5+bHvO+eFPA+bt0iC1zkQMoU/iGdRVjcRbw==}
dependencies:
'@types/node': 18.15.11
source-map: 0.6.1
dev: true
/@types/html-minifier@4.0.2:
resolution: {integrity: sha512-4IkmkXJP/25R2fZsCHDX2abztXuQRzUAZq39PfCMz2loLFj8vS9y7aF6vDl58koXSTpsF+eL4Lc5Y4Aww/GCTQ==}
dependencies:
'@types/clean-css': 4.2.6
'@types/relateurl': 0.2.29
'@types/uglify-js': 3.17.1
dev: true
/@types/http-cache-semantics@4.0.1: /@types/http-cache-semantics@4.0.1:
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
dev: true dev: true
/@types/node@18.15.11:
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
dev: true
/@types/relateurl@0.2.29:
resolution: {integrity: sha512-QSvevZ+IRww2ldtfv1QskYsqVVVwCKQf1XbwtcyyoRvLIQzfyPhj/C+3+PKzSDRdiyejaiLgnq//XTkleorpLg==}
dev: true
/@types/uglify-js@3.17.1:
resolution: {integrity: sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==}
dependencies:
source-map: 0.6.1
dev: true
/@vincjo/datatables@1.5.2:
resolution: {integrity: sha512-pdpwJ0y/CobZdfIxeujXB1jIARVlaKRf0aGhr6cINdUTuW0Ab0kgpZNy/MdAXnKeE6lBFzBXjI8FcJzkdmN/wg==}
dev: false
/acorn-walk@8.2.0: /acorn-walk@8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
@@ -892,6 +850,11 @@ packages:
run-applescript: 5.0.0 run-applescript: 5.0.0
dev: true dev: true
/bwip-js@3.4.0:
resolution: {integrity: sha512-Gx9LIBhmEFmNH4FJsS+Rs+bG5hUcs+OBemEEQ2ZTLz8tue0PA/lM692Gf2yuYJ2yUpLGtK9tAexs85tXBPG/ww==}
hasBin: true
dev: false
/bytes@3.1.2: /bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -927,13 +890,6 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/camel-case@3.0.0:
resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==}
dependencies:
no-case: 2.3.2
upper-case: 1.1.3
dev: true
/camelcase-css@2.0.1: /camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -998,13 +954,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/clean-css@4.2.4:
resolution: {integrity: sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==}
engines: {node: '>= 4.0'}
dependencies:
source-map: 0.6.1
dev: true
/cli-boxes@3.0.0: /cli-boxes@3.0.0:
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1078,10 +1027,6 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true dev: true
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: true
/commander@4.1.1: /commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -1905,25 +1850,6 @@ packages:
function-bind: 1.1.1 function-bind: 1.1.1
dev: true dev: true
/he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
dev: true
/html-minifier@4.0.0:
resolution: {integrity: sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==}
engines: {node: '>=6'}
hasBin: true
dependencies:
camel-case: 3.0.0
clean-css: 4.2.4
commander: 2.20.3
he: 1.2.0
param-case: 2.1.1
relateurl: 0.2.7
uglify-js: 3.17.4
dev: true
/http-cache-semantics@4.1.1: /http-cache-semantics@4.1.1:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
dev: true dev: true
@@ -2481,10 +2407,6 @@ packages:
is-unicode-supported: 1.3.0 is-unicode-supported: 1.3.0
dev: true dev: true
/lower-case@1.1.4:
resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==}
dev: true
/lowercase-keys@3.0.0: /lowercase-keys@3.0.0:
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -2647,12 +2569,6 @@ packages:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
dev: false dev: false
/no-case@2.3.2:
resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}
dependencies:
lower-case: 1.1.4
dev: true
/node-domexception@1.0.0: /node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'} engines: {node: '>=10.5.0'}
@@ -2852,12 +2768,6 @@ packages:
semver: 7.3.8 semver: 7.3.8
dev: true dev: true
/param-case@2.1.1:
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
dependencies:
no-case: 2.3.2
dev: true
/parent-module@1.0.1: /parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -3152,11 +3062,6 @@ packages:
rc: 1.2.8 rc: 1.2.8
dev: true dev: true
/relateurl@0.2.7:
resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
engines: {node: '>= 0.10'}
dev: true
/release-it@15.10.1: /release-it@15.10.1:
resolution: {integrity: sha512-Wkk4aFHSo27vQwHIlcEy77lJwnQlh4UDQckc53gh5tKo7F22mAUEAe8SYQZJcFh7icdkf0OV70onhB1dDmeClA==} resolution: {integrity: sha512-Wkk4aFHSo27vQwHIlcEy77lJwnQlh4UDQckc53gh5tKo7F22mAUEAe8SYQZJcFh7icdkf0OV70onhB1dDmeClA==}
engines: {node: '>=14.9'} engines: {node: '>=14.9'}
@@ -3728,7 +3633,9 @@ packages:
resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
hasBin: true hasBin: true
requiresBuild: true
dev: true dev: true
optional: true
/unbox-primitive@1.0.2: /unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
@@ -3796,10 +3703,6 @@ packages:
xdg-basedir: 5.1.0 xdg-basedir: 5.1.0
dev: true dev: true
/upper-case@1.1.3:
resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==}
dev: true
/url-join@5.0.0: /url-join@5.0.0:
resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}

File diff suppressed because one or more lines are too long

View File

@@ -4,11 +4,13 @@
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}`,
{ {

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 { import {
RunnerCardService, RunnerCardService,
RunnerService, RunnerService,
@@ -9,9 +9,10 @@
} 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 dispatch = createEventDispatcher();
const getRunnerLabel = (option) => { const getRunnerLabel = (option) => {
if (option.middlename) { if (option.middlename) {
return option.firstname + " " + option.middlename + " " + option.lastname; return option.firstname + " " + option.middlename + " " + option.lastname;
@@ -21,7 +22,7 @@
const filterRunners = (label, filterText, option) => { const filterRunners = (label, filterText, option) => {
if (filterText.startsWith("#")) { if (filterText.startsWith("#")) {
return option.value.id == parseInt(filterText.replace("#","")) return option.value.id == parseInt(filterText.replace("#", ""));
} }
return ( return (
label.toLowerCase().includes(filterText.toLowerCase()) || label.toLowerCase().includes(filterText.toLowerCase()) ||
@@ -79,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) => {
// //
@@ -97,7 +97,6 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
modal_open = false; modal_open = false;

View File

@@ -7,7 +7,6 @@
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte"; 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 = {};
@@ -30,9 +29,6 @@
$: enabled = true; $: enabled = true;
$: processed_last_submit = true; $: processed_last_submit = true;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
function dataUpdated() {
dispatch('dataUpdated',);
}
RunnerService.runnerControllerGetAll().then((val) => { RunnerService.runnerControllerGetAll().then((val) => {
runners = val.map((r) => { runners = val.map((r) => {
return { label: getRunnerLabel(r), value: r }; return { label: getRunnerLabel(r), value: r };
@@ -75,9 +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;
dataUpdated();
}) })
.catch((err) => { .catch((err) => {
// //

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

@@ -3,35 +3,129 @@
import { RunnerCardService } from "@odit/lfk-client-js"; import { RunnerCardService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { DataHandler, Datatable, Th, ThFilter } from "@vincjo/datatables";
import CardsEmptyState from "./CardsEmptyState.svelte"; import CardsEmptyState from "./CardsEmptyState.svelte";
import CardDetailModal from "./CardDetailModal.svelte"; import CardDetailModal from "./CardDetailModal.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte"; import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import ThFilterStatus from "./ThFilterStatus.svelte"; import InputElement from "../shared/InputElement.svelte";
import ThFilterRunner from "./ThFilterRunner.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 = [];
const handler = new DataHandler(current_cards, { rowsPerPage: 50 }); export const addCards = (cards) => {
const rows = handler.getRows(); console.log(cards);
$: active_deletes = []; current_cards = current_cards.concat(...cards);
options.update((options) => ({
...options,
data: current_cards,
}));
};
$: 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; $: cards_show = generate_cards.length > 0;
$: generate_cards = []; $: generate_cards = [];
const cards_promise = RunnerCardService.runnerCardControllerGetAll().then(
(val) => { const columns = [
current_cards = val; {
handler.setRows(val); accessorKey: "code",
} header: () => $_("code"),
); filterFn: `includesString`,
const getRunnerLabel = (option) => },
option?.firstname + {
" " + accessorKey: "runner",
(option?.middlename || "") + header: () => $_("runner"),
" " + cell: (info) => {
(option?.lastname || "{$_('non-blanko')}"); 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(),
});
const table = createSvelteTable(options);
function open_edit_modal(card) { function open_edit_modal(card) {
const getRunnerLabel = (option) =>
option.firstname +
" " +
(option.middlename || "") +
" " +
option.lastname;
if (card.runner?.id) { if (card.runner?.id) {
runner = Object.assign( runner = Object.assign(
{ runner }, { runner },
@@ -46,21 +140,73 @@
original_data = Object.assign(original_data, card); original_data = Object.assign(original_data, card);
edit_modal_open = true; edit_modal_open = true;
} }
async function deleteCard(delete_card_id) {
await RunnerCardService.runnerCardControllerRemove(delete_card_id, true);
current_cards = current_cards.filter((r) => r.id !== delete_card_id);
options.update((options) => ({
...options,
data: current_cards,
}));
Toastify({
text: $_("card-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
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>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:UPDATE")} {#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={handler.setRows(current_cards)} 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"
@@ -68,205 +214,102 @@
<p class="font-bold">{$_("loading-cards")}</p> <p class="font-bold">{$_("loading-cards")}</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_cards.length === 0}
{#if current_cards.length === 0} <CardsEmptyState />
<CardsEmptyState /> {:else}
{:else} <div class="h-12 mt-1">
<div class="h-12 mt-1"> {#if selected.length > 0}
{#if cards_show} <button
<button 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 inline-flex"
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" id="options-menu"
id="options-menu" on:click={async () => {
on:click={async () => { const prom = [];
const prom = []; for (const card of selectedCards) {
for (const card of generate_cards) { prom.push(
prom.push( await RunnerCardService.runnerCardControllerRemove(
RunnerCardService.runnerCardControllerRemove(card.id, true) card.id,
); true
} )
await Promise.all(prom); );
Toastify({ }
text: $_("cards-deleted"), await Promise.all(prom);
duration: 3500, for (const card of selectedCards) {
backgroundColor: current_cards = current_cards.filter((r) => r.id !== card.id);
"linear-gradient(to right, #00b09b, #96c93d)", }
}).showToast(); options.update((options) => ({
//TODO: Delete cards from table ...options,
}} data: current_cards,
}));
$table.resetRowSelection();
}}
>
{$_("delete-cards")}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
> >
{$_("delete-cards")} <path
<svg stroke-linecap="round"
xmlns="http://www.w3.org/2000/svg" stroke-linejoin="round"
fill="none" 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"
viewBox="0 0 24 24" />
stroke-width="1.5" </svg>
stroke="currentColor" </button>
class="w-5 h-5" {/if}
> <GenerateRunnerCards
<path cards_show={selected.length > 0}
stroke-linecap="round" bind:generate_cards={selectedCards}
stroke-linejoin="round" />
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" </div>
/> <div class="overflow-x-auto">
</svg> <table class="w-full">
</button> <thead>
{/if} {#each $table.getHeaderGroups() as headerGroup}
<GenerateRunnerCards bind:cards_show bind:generate_cards /> <tr class="select-none">
</div> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
<Datatable {handler}> <InputElement
<table>
<thead>
<tr>
<th style="border-bottom: 1px solid #ddd;">
<input
type="checkbox" type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" checked={$table.getIsAllRowsSelected()}
checked={generate_cards.length == current_cards.length} indeterminate={$table.getIsSomeRowsSelected()}
on:click={() => { on:change={() => $table.toggleAllRowsSelected()}
if (generate_cards.length != current_cards.length) {
generate_cards = current_cards;
} else {
generate_cards = [];
}
}}
/> />
</th> </th>
<Th {handler} orderBy="code">{$_("code")}</Th> {#each headerGroup.headers as header}
<Th {handler} orderBy="runner">{$_("runner")}</Th> <TableHeader {header} />
<Th {handler} orderBy="status">{$_("status")}</Th> {/each}
<th style="border-bottom: 1px solid #ddd;">{$_("action")}</th>
</tr> </tr>
{/each}
</thead>
<tbody>
{#each $table.getRowModel().rows as row}
<tr> <tr>
<th style="border-bottom: 1px solid #ddd;" /> <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
<ThFilter {handler} filterBy="code" /> <InputElement
<ThFilterRunner {handler} /> type="checkbox"
<ThFilterStatus {handler} /> checked={row.getIsSelected()}
<th style="border-bottom: 1px solid #ddd;" /> on:change={() => row.toggleSelected()}
</tr> />
</thead> </td>
<tbody> {#each row.getVisibleCells() as cell}
{#each $rows as row}
<tr>
<td> <td>
<input <svelte:component
type="checkbox" this={flexRender(
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" cell.column.columnDef.cell,
checked={generate_cards.filter((i) => i.id == row.id) cell.getContext()
.length > 0} )}
on:click={() => {
if (
generate_cards.findIndex((i) => i.id == row.id) == -1
) {
generate_cards.push(row);
generate_cards = generate_cards;
} else {
generate_cards = generate_cards.filter(
(r) => r.id != row.id
);
}
console.log(generate_cards);
}}
/> />
</td> </td>
<td>{row.code}</td> {/each}
<td> </tr>
{#if row.runner} {/each}
<a </tbody>
href="../runners/{row.runner.id}" </table>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{row.runner.firstname}
{row.runner.middlename || ""}
{row.runner.lastname}</a
>
{:else}{$_("non-blanko")}{/if}
</td>
<td>
{#if row.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}
</td>
<td>
{#if active_deletes[row.id] === true}
<button
on:click={() => {
active_deletes[row.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
RunnerCardService.runnerCardControllerRemove(
row.id,
true
)
.then((resp) => {
current_cards = current_cards.filter(
(obj) => obj.id !== row.id
);
})
.catch((err) => {});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
{:else}
<button
on:click={() => {
open_edit_modal(row);
}}
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</button
>
{#if store.state.jwtinfo.userdetails.permissions.includes("CARD:DELETE")}
<button
on:click={() => {
active_deletes[row.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</Datatable>
{/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} <TableBottom {table} {selected} />
{/if}
{/if} {/if}
<style>
table tbody {
display: block;
overflow-y: scroll;
}
table thead,
table tbody tr {
display: table;
width: 100%;
table-layout: fixed;
}
</style>

View File

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

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

@@ -35,7 +35,9 @@
<p class="text-sm">{$_("this-might-take-a-moment")}</p> <p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:then stats} {: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"> <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 <StatCard
title={$_("runners")} title={$_("runners")}
value={stats.total_runners} value={stats.total_runners}
@@ -59,18 +61,67 @@
href="/scans/" href="/scans/"
> >
<svg <svg
stroke="currentColor"
fill="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" width="24"
height="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
><polyline points="22 12 18 12 15 21 9 3 6 12 2 12" /></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>
<StatCard <StatCard
@@ -105,6 +156,22 @@
/></svg /></svg
> >
</StatCard> </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 <StatCard
title={$_("count_teams")} title={$_("count_teams")}
value={stats.total_teams} value={stats.total_teams}

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 { import {
DonationService, DonationService,
DonorService, DonorService,
@@ -9,9 +9,10 @@
} 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 { is_promise } from "svelte/internal"; import { is_promise } from "svelte/internal";
import { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
export let current_donations; const dispatch = createEventDispatcher();
const getDonorLabel = (option) => const getDonorLabel = (option) =>
option.firstname + " " + (option.middlename || "") + " " + option.lastname; option.firstname + " " + (option.middlename || "") + " " + option.lastname;
const filterDonors = (label, filterText, option) => const filterDonors = (label, filterText, option) =>
@@ -59,16 +60,16 @@ import { is_promise } from "svelte/internal";
let amount_cent = Math.floor(amount_input * 100); let amount_cent = Math.floor(amount_input * 100);
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: $_('adding-donation'), text: $_("adding-donation"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
if (is_fixed) { if (is_fixed) {
let postdata = { let postdata = {
donor, donor,
amount: amount_cent, amount: amount_cent,
paidAmount: 0 paidAmount: 0,
}; };
if(is_paid){ if (is_paid) {
postdata.paidAmount = amount_cent; postdata.paidAmount = amount_cent;
} }
DonationService.donationControllerPostFixed(postdata) DonationService.donationControllerPostFixed(postdata)
@@ -79,12 +80,11 @@ import { is_promise } from "svelte/internal";
modal_open = false; modal_open = false;
// //
Toastify({ Toastify({
text: $_('donation_added'), text: $_("donation_added"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_donations.push(result); dispatch("created", { donations: [result] });
current_donations = current_donations;
}) })
.catch((err) => { .catch((err) => {
// //
@@ -108,12 +108,11 @@ import { is_promise } from "svelte/internal";
modal_open = false; modal_open = false;
// //
Toastify({ Toastify({
text: $_('donation_added'), text: $_("donation_added"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_donations.push(result); dispatch("created", { donations: [result] });
current_donations = current_donations;
}) })
.catch((err) => { .catch((err) => {
// //
@@ -128,6 +127,207 @@ import { is_promise } from "svelte/internal";
} }
</script> </script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}
>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="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
>
</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">
{#if is_fixed}
{$_("create-a-new-fixed-donation")}
{:else}{$_("create-a-new-distance-donation")}{/if}
</h3>
<label class="content-center align-middle object-center">
<span class="ml-2 text-base" class:text-gray-300={is_fixed}
>{$_("distance-donation")}</span
>
<input
class="toggle relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle"
type="checkbox"
bind:checked={is_fixed}
/>
<span class="ml-2 text-base" class:text-gray-300={!is_fixed}
>{$_("fixed-donation")}</span
>
</label>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"please-provide-the-nessecary-information-to-create-a-new-donation"
)}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700"
>{$_("donor")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={donors}
showChevron={true}
placeholder={$_("search-for-donor-name-or-id")}
noOptionsMessage={$_("no-donors-found")}
on:select={(selectedValue) =>
(donor = selectedValue.detail.value.id)}
on:clear={() => (donors = null)}
/>
</div>
{#if !is_fixed}
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700"
>{$_("runner")}</label
>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) =>
filterDonors(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_("search-for-runner-by-name-or-id")}
noOptionsMessage={$_("no-runners-found")}
on:select={(selectedValue) =>
(runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)}
/>
</div>
{/if}
<div class="col-span-6">
<label
for="donation_amount_eur"
class="block text-sm font-medium text-gray-700"
>
{#if !is_fixed}
{$_("amount-per-kilometer")}
{:else}{$_("donation-amount")}{/if}</label
>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="2.00"
/>
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
></span
>
</div>
{#if !is_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1"
>
{$_("donation-amount-must-be-greater-that-0-00eur")}
</span>
{/if}
</div>
{#if is_fixed}
<div class="col-span-6">
<label
for="paid"
class="block text-sm font-medium text-gray-700"
>{$_("already-paid")}</label
>
<p class="text-gray-500">
<input
id="paid"
bind:checked={is_paid}
name="paid"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
<span class="align-text-bottom">
{#if is_paid}
{$_("paid")}
{:else}
{$_("open")}
{/if}
</span>
</p>
</div>
{/if}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create")}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("cancel")}
</button>
</div>
</div>
</div>
</div>
{/if}
<style> <style>
.toggle:before { .toggle:before {
content: ""; content: "";
@@ -152,173 +352,3 @@ import { is_promise } from "svelte/internal";
left: 1.25rem; left: 1.25rem;
} }
</style> </style>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="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>
</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">
{#if is_fixed}
{$_('create-a-new-fixed-donation')}
{:else}{$_('create-a-new-distance-donation')}{/if}
</h3>
<label class="content-center align-middle object-center">
<span
class="ml-2 text-base"
class:text-gray-300={is_fixed}>{$_('distance-donation')}</span>
<input
class="toggle relative w-10 h-5 transition-all duration-200 ease-in-out bg-gray-400 rounded-full shadow-inner outline-none appearance-none align-middle"
type="checkbox"
bind:checked={is_fixed} />
<span
class="ml-2 text-base "
class:text-gray-300={!is_fixed}>{$_('fixed-donation')}</span>
</label>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('please-provide-the-nessecary-information-to-create-a-new-donation')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('donor')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)}
items={donors}
showChevron={true}
placeholder={$_('search-for-donor-name-or-id')}
noOptionsMessage={$_('no-donors-found')}
on:select={(selectedValue) => (donor = selectedValue.detail.value.id)}
on:clear={() => (donors = null)} />
</div>
{#if !is_fixed}
<div class="col-span-6">
<label
for="donor"
class="block text-sm font-medium text-gray-700">{$_('runner')}</label>
<Select
containerClasses="rounded-l-md mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2"
itemFilter={(label, filterText, option) => filterDonors(label, filterText, option)}
items={runners}
showChevron={true}
placeholder={$_('search-for-runner-by-name-or-id')}
noOptionsMessage={$_('no-runners-found')}
on:select={(selectedValue) => (runner = selectedValue.detail.value.id)}
on:clear={() => (runner = null)} />
</div>
{/if}
<div class="col-span-6">
<label
for="donation_amount_eur"
class="block text-sm font-medium text-gray-700">
{#if !is_fixed}
{$_('amount-per-kilometer')}
{:else}{$_('donation-amount')}{/if}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!is_amount_valid}
class:focus:border-red-500={!is_amount_valid}
class:focus:ring-red-500={!is_amount_valid}
bind:value={amount_input}
type="number"
step="0.01"
name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="2.00" />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"></span>
</div>
{#if !is_amount_valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('donation-amount-must-be-greater-that-0-00eur')}
</span>
{/if}
</div>
{#if is_fixed}
<div class="col-span-6">
<label
for="paid"
class="block text-sm font-medium text-gray-700">{$_('already-paid')}</label>
<p class="text-gray-500">
<input
id="paid"
bind:checked={is_paid}
name="paid"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" >
<span class="align-text-bottom">
{#if is_paid}
{$_('paid')}
{:else}
{$_('open')}
{/if}
</span>
</p>
</div>
{/if}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -1,19 +1,21 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { DonationService } from "@odit/lfk-client-js"; import { DonationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let payment_modal_open = false; export let payment_modal_open = false;
export let current_donations = [];
export let editable = {};
export let original_data = {}; export let original_data = {};
export let paid_amount_input = 0; export let paid_amount_input = 0;
$:processed_last_submit=true; const dispatch = createEventDispatcher();
$: processed_last_submit = true;
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
$: createbtnenabled = is_paid_amount_valid && !(paid_amount_input*100 == original_data.paidAmount) $: createbtnenabled =
is_paid_amount_valid &&
!(paid_amount_input * 100 == original_data.paidAmount);
$: is_paid_amount_valid = paid_amount_input > 0; $: is_paid_amount_valid = paid_amount_input > 0;
(() => { (() => {
document.onkeydown = (e) => { document.onkeydown = (e) => {
@@ -33,61 +35,56 @@
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: $_('updating-donation'), text: $_("updating-donation"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
const editable = Object.assign({}, original_data);
editable.donor = editable.donor.id; editable.donor = editable.donor.id;
editable.paidAmount = paid_amount_input*100; editable.paidAmount = paid_amount_input * 100;
if(editable.responseType == "DISTANCEDONATION" || editable.runner){ if (editable.responseType == "DISTANCEDONATION" || editable.runner) {
editable.runner = editable.runner.id; editable.runner = editable.runner.id;
DonationService.donationControllerPutDistance(original_data.id, editable) DonationService.donationControllerPutDistance(
.then((result) => { original_data.id,
let id = original_data.id; editable
editable = {}; )
original_data = {}; .then((result) => {
payment_modal_open = false; payment_modal_open = false;
// //
Toastify({ Toastify({
text: $_('donation-updated'), text: $_("donation-updated"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_donations[current_donations.findIndex((c) => c.id === id)] = result; dispatch("created", { donation: response });
current_donations = current_donations; })
}) .catch((err) => {
.catch((err) => { //
// })
}) .finally(() => {
.finally(() => { processed_last_submit = true;
processed_last_submit = true; //
// toast.hideToast();
toast.hideToast(); });
}); } else {
} DonationService.donationControllerPutFixed(original_data.id, editable)
else{ .then((result) => {
DonationService.donationControllerPutFixed(original_data.id, editable) payment_modal_open = false;
.then((result) => { //
let id = original_data.id; Toastify({
editable = {}; text: $_("donation-updated"),
original_data = {}; duration: 500,
payment_modal_open = false; backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
// }).showToast();
Toastify({ dispatch("created", { donation: response });
text: $_('donation-updated'), })
duration: 500, .catch((err) => {
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", //
}).showToast(); })
current_donations[current_donations.findIndex((c) => c.id === id)] = result; .finally(() => {
current_donations = current_donations; processed_last_submit = true;
}) //
.catch((err) => { toast.hideToast();
// });
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
} }
} }
} }
@@ -96,57 +93,71 @@
{#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:clickOutside use:clickOutside
on:click_outside={() => { on:click_outside={() => {
payment_modal_open = false; payment_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">
{$_('enter-payment')} {$_("enter-payment")}
</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-enter-the-donations-paid-amount-manually-or-use-the-max-button-to-use-the-donations-exact-amount')} {$_(
"you-can-enter-the-donations-paid-amount-manually-or-use-the-max-button-to-use-the-donations-exact-amount"
)}
</p> </p>
</div> </div>
<div class="grid grid-cols gap-6"> <div class="grid grid-cols gap-6">
<div class="w-full"> <div class="w-full">
<label <label
for="token" for="token"
class="block text-sm font-medium text-gray-700">{$_('paid-amount')}</label> class="block text-sm font-medium text-gray-700"
<div class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full"> >{$_("paid-amount")}</label
<input >
<div
class="inline-flex border-gray-300 border rounded-l-md rounded-r-md bg-gray-50 text-gray-500 w-full"
>
<input
autocomplete="off" autocomplete="off"
class:border-red-500={!is_paid_amount_valid} class:border-red-500={!is_paid_amount_valid}
class:focus:border-red-500={!is_paid_amount_valid} class:focus:border-red-500={!is_paid_amount_valid}
@@ -156,47 +167,55 @@
step="0.01" step="0.01"
name="donation_amount_eur" name="donation_amount_eur"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm p-2" class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm p-2"
placeholder="2.00" /> placeholder="2.00"
/>
<button <button
on:click={ on:click={() => {
()=>{ paid_amount_input = paid_amount_input = (
paid_amount_input=paid_amount_input = (original_data.amount/100).toFixed(2); original_data.amount / 100
} ).toFixed(2);
} }}
class="inline-flex items-center p-r-2 text-indigo-300 hover:text-indigo-700 text-sm">MAX</button> class="inline-flex items-center p-r-2 text-indigo-300 hover:text-indigo-700 text-sm"
>MAX</button
>
<span <span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"></span> class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm"
></span
>
</div> </div>
{#if !is_paid_amount_valid} {#if !is_paid_amount_valid}
<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"
{$_('payment-amount-must-be-greater-than-0-00eur')} >
{$_("payment-amount-must-be-greater-than-0-00eur")}
</span> </span>
{/if} {/if}
</div> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> </div>
<button <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
disabled={!createbtnenabled} <button
class:opacity-50={!createbtnenabled} disabled={!createbtnenabled}
on:click={submit} class:opacity-50={!createbtnenabled}
type="button" on:click={submit}
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"> type="button"
{$_('save-changes')} class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
</button> >
<button {$_("save-changes")}
on:click={() => { </button>
payment_modal_open = false; <button
}} on:click={() => {
type="button" payment_modal_open = false;
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')} type="button"
</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"
</div> >
{$_("cancel")}
</button>
</div> </div>
</div> </div>
</div> </div>
</div>
{/if} {/if}

View File

@@ -0,0 +1,122 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { createEventDispatcher, onMount } from "svelte";
export let modal_open;
export let delete_donation = {
id: 0,
runner: {
firstname: "",
lastname: "",
},
donor: {
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_donation.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
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
>
</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-donation")}
</p>
</div>
<div class="w-full">
<span class="inline-block"
><b>{$_("donor")}</b>: {delete_donation.donor.firstname}
{delete_donation.donor.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

@@ -0,0 +1,18 @@
<script>
import { _ } from "svelte-i18n";
export let donor;
</script>
{#if !donor || donor.firstname == 0}
{$_("donor-has-no-associated-donations")}
{:else}
<div class="flex items-center">
<a
href="../donors/{donor.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{donor.firstname}
{#if donor.middlename}{donor.middlename}{/if}
{donor.lastname}</a
>
</div>
{/if}

View File

@@ -0,0 +1,18 @@
<script>
import { _ } from "svelte-i18n";
export let runner;
</script>
{#if !runner || runner.firstname == 0}
{$_("fixed-donation")}
{:else}
<div class="text-sm font-medium text-gray-900">
<a
href="../runners/{runner.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>{runner.firstname}
{#if runner.middlename}{runner.middlename}{/if}
{runner.lastname}</a
>
</div>
{/if}

View File

@@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
export let status;
</script>
{#if 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}

View File

@@ -0,0 +1,21 @@
<script>
import { _ } from "svelte-i18n";
import TableActions from "../shared/TableActions.svelte";
export let detailsLink;
export let detailsAction;
export let deleteEnabled;
export let deleteAction;
export let paymentAction;
</script>
<button
on:click={paymentAction}
class="text-[#025a21] hover:text-green-900 mr-4">{$_("enter-payment")}</button
>
<TableActions
bind:detailsAction
bind:detailsLink
bind:deleteAction
bind:deleteEnabled
/>

View File

@@ -5,25 +5,33 @@
import DonationsOverview from "./DonationsOverview.svelte"; import DonationsOverview from "./DonationsOverview.svelte";
$: current_donations = []; $: current_donations = [];
export let modal_open = false; export let modal_open = false;
let addDonations;
</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">
{$_('donations')} {$_("donations")}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION: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-donation')} >
{$_("add-donation")}
</button> </button>
{/if} {/if}
</span> </span>
<DonationsOverview bind:current_donations /> <DonationsOverview bind:current_donations bind:addDonations />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONATION:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("DONATION:CREATE")}
<AddDonationModal bind:current_donations bind:modal_open /> <AddDonationModal
on:created={(event) => {
console.log(event)
addDonations(event.detail.donations);
}}
bind:modal_open
/>
{/if} {/if}

View File

@@ -1,235 +1,284 @@
<script> <script>
import { getLocaleFromNavigator, _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { DonationService, DonorService } from "@odit/lfk-client-js"; import { DonationService } from "@odit/lfk-client-js";
import store from "../../store"; import store from "../../store";
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";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import TableBottom from "../shared/TableBottom.svelte";
import InputElement from "../shared/InputElement.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import DonationDonor from "./DonationDonor.svelte";
import DonationRunner from "./DonationRunner.svelte";
import DonationStatus from "./DonationStatus.svelte";
import DonationTableAction from "./DonationTableAction.svelte";
import DeleteDonationModal from "./DeleteDonationModal.svelte";
import { donationDonorFilter, donationRunnerFilter } from "../shared/tablefilters";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: active_edits = [];
$: selectedDonations =
$table?.getSelectedRowModel().rows.map((row) => row.original) || [];
$: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false;
export let current_donations = []; export let current_donations = [];
export let payment_modal_open = false; export const addDonations = (donations) => {
export let editable = {}; current_donations = current_donations.concat(...donations);
export let original_data = {}; options.update((options) => ({
export let paid_amount_input = 0; ...options,
const donations_promise = DonationService.donationControllerGetAll().then( data: current_donations,
(val) => { }));
current_donations = val; };
}
); //Section table
function should_display_based_on_id(id) { const columns = [
if (searchvalue.toString().slice(-1) === "*") { {
return id.toString().startsWith(searchvalue.replace("*", "")); accessorKey: "id",
} header: () => "id",
return id.toString() === searchvalue; filterFn: `equalsString`,
} },
function open_payment_modal(donation) { {
editable = Object.assign({}, donation); accessorKey: "donor",
original_data = Object.assign({}, donation); header: () => $_("donor"),
paid_amount_input = (donation.paidAmount/100).toFixed(2); cell: (info) => {
payment_modal_open = true; return renderComponent(DonationDonor, { donor: info.getValue() });
},
filterFn: `donor`,
},
{
accessorKey: "runner",
header: () => $_("runner"),
cell: (info) => {
return renderComponent(DonationRunner, { runner: info.getValue() });
},
filterFn: `runner`,
},
{
accessorKey: "amountPerDistance",
header: () => $_("amount-per-kilometer"),
cell: (info) => {
if (!info.getValue()) {
return $_("fixed-donation");
}
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "amount",
header: () => $_("donation-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "paidAmount",
header: () => $_("total-paid-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })} €`;
},
enableColumnFilter: false,
},
{
accessorKey: "status",
header: () => $_("status"),
cell: (info) => {
return renderComponent(DonationStatus, { status: info.getValue() });
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(DonationTableAction, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_deletes = current_donations.filter(
(r) => r.id == info.row.original.id
);
},
paymentAction: () => {
active_edits = current_donations.filter(
(r) => r.id == info.row.original.id
);
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"DONATION:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
donor: donationDonorFilter,
runner: donationRunnerFilter,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
const table = createSvelteTable(options);
async function deleteDonation(delete_donation_id) {
await DonationService.donationControllerRemove(delete_donation_id, true);
current_donations = current_donations.filter(
(r) => r.id !== delete_donation_id
);
options.update((options) => ({
...options,
data: current_donations,
}));
Toastify({
text: $_("donation-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
} }
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);
options.update((options) => ({
...options,
data: current_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')} original_data={active_edits[0]}
{#await donations_promise} payment_modal_open={active_edits.length > 0}
paid_amount_input={(active_edits[0]?.paidAmount || 0) / 100}
on:created={(event) => {
current_donations[
current_donations.findIndex((d) => d.id === event.detail.donation.id)
].paidAmount = event.detail.donation.paidAmount;
options.update((options) => ({
...options,
data: current_donations,
}));
}}
/>
<DeleteDonationModal
delete_donation={active_deletes[0]}
modal_open={active_deletes.length > 0}
on:delete={(event) => {
deleteDonation(event.detail.id);
}}
/>
{#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="text-sm">{$_('this-might-take-a-moment')}</p> <p class="font-bold">{$_("donations-are-being-loaded")}</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="w-full">
<tr> <thead>
<th {#each $table.getHeaderGroups() as headerGroup}
scope="col" <tr class="select-none">
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
{$_('donor')} <InputElement
</th> type="checkbox"
<th checked={$table.getIsAllRowsSelected()}
scope="col" indeterminate={$table.getIsSomeRowsSelected()}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> on:change={() => $table.toggleAllRowsSelected()}
{$_('runner')} />
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('amount-per-kilometer')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donation-amount')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('paid-amount')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('status')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th> </th>
{#each headerGroup.headers as header}
<TableHeader {header} />
{/each}
</tr> </tr>
</thead> {/each}
<tbody class="divide-y divide-gray-200"> </thead>
{#each current_donations as donation} <tbody>
{#if donation.donor.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
) || donation.donor.lastname type="checkbox"
.toLowerCase() checked={row.getIsSelected()}
.includes( on:change={() => row.toggleSelected()}
searchvalue.toLowerCase() />
) || donation.runner?.firstname </td>
.toLowerCase() {#each row.getVisibleCells() as cell}
.includes( <td>
searchvalue.toLowerCase() <svelte:component
) || donation.runner?.lastname this={flexRender(
.toLowerCase() cell.column.columnDef.cell,
.includes( cell.getContext()
searchvalue.toLowerCase() )}
) || should_display_based_on_id(donation.id)} />
<tr data-rowid="donation_{donation.id}"> </td>
<td class="px-6 py-4 whitespace-nowrap"> {/each}
<div class="flex items-center"> </tr>
<a {/each}
href="../donors/{donation.donor.id}" </tbody>
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{donation.donor.firstname} </table>
{donation.donor.middlename || ''}
{donation.donor.lastname}</a>
</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}
<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>
{/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" />
<TableBottom {table} {selected} />
{/if}
{/if} {/if}

View File

@@ -1,15 +1,13 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
import { import { DonorService } from "@odit/lfk-client-js";
DonorService
} from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail"; import isEmail from "validator/es/lib/isEmail";
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 { createEventDispatcher } from "svelte";
export let modal_open; export let modal_open;
export let current_donors;
let firstname_input; let firstname_input;
let lastname_input; let lastname_input;
let middlename_input; let middlename_input;
@@ -19,6 +17,7 @@
let address_input2; let address_input2;
let address_zipcode; let address_zipcode;
let address_city; let address_city;
const dispatch = createEventDispatcher();
function focus(el) { function focus(el) {
el.focus(); el.focus();
} }
@@ -75,7 +74,7 @@
if (processed_last_submit === true) { if (processed_last_submit === true) {
processed_last_submit = false; processed_last_submit = false;
const toast = Toastify({ const toast = Toastify({
text: $_('donor-is-being-added'), text: $_("donor-is-being-added"),
duration: -1, duration: -1,
}).showToast(); }).showToast();
let address = {}; let address = {};
@@ -92,7 +91,7 @@
firstname: firstname_input_value, firstname: firstname_input_value,
lastname: lastname_input_value, lastname: lastname_input_value,
address, address,
receiptNeeded: address_checked receiptNeeded: address_checked,
}; };
if (middlename_input_value) { if (middlename_input_value) {
postdata.middlename = middlename_input_value; postdata.middlename = middlename_input_value;
@@ -112,12 +111,11 @@
modal_open = false; modal_open = false;
// //
Toastify({ Toastify({
text: $_('donor-added'), text: $_("donor-added"),
duration: 500, duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast(); }).showToast();
current_donors.push(result); dispatch("created", { donors: [result] });
current_donors = current_donors;
}) })
.catch((err) => { .catch((err) => {
// //
@@ -134,58 +132,70 @@
{#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: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 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" viewBox="0 0 24 24"
width="24" width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" /> height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path <path
d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" /></svg> d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"
/></svg
>
</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-donor')} {$_("create-a-new-donor")}
</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">
{$_('please-provide-the-nessecary-information-to-add-a-new-donor')} {$_(
"please-provide-the-nessecary-information-to-add-a-new-donor"
)}
</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="firstname" for="firstname"
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("first-name")}</label
>
<input <input
use:focus use:focus
autocomplete="off" autocomplete="off"
placeholder={$_('first-name')} placeholder={$_("first-name")}
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}
@@ -193,34 +203,41 @@
bind:this={firstname_input} bind:this={firstname_input}
type="text" type="text"
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="col-span-6"> <div class="col-span-6">
<label <label
for="trackname" for="trackname"
class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("middle-name")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('middle-name')} placeholder={$_("middle-name")}
bind:value={middlename_input_value} bind:value={middlename_input_value}
bind:this={middlename_input} bind:this={middlename_input}
type="text" type="text"
name="trackname" name="trackname"
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="lastname" for="lastname"
class="block text-sm font-medium text-gray-700">{$_('last-name')}</label> class="block text-sm font-medium text-gray-700"
>{$_("last-name")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder="{$_('last-name')}" placeholder={$_("last-name")}
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}
@@ -228,21 +245,25 @@
bind:this={lastname_input} bind:this={lastname_input}
type="text" type="text"
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="col-span-6"> <div class="col-span-6">
<label <label
for="phone" for="phone"
class="block text-sm font-medium text-gray-700">{$_('phone')}</label> class="block text-sm font-medium text-gray-700"
>{$_("phone")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('phone')} placeholder={$_("phone")}
class:border-red-500={!isPhoneValidOrEmpty} class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty} class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty} class:focus:ring-red-500={!isPhoneValidOrEmpty}
@@ -250,21 +271,27 @@
bind:this={phone_input} bind:this={phone_input}
type="tel" type="tel"
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"
/>
{#if !isPhoneValidOrEmpty} {#if !isPhoneValidOrEmpty}
<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"
{@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')} >
{@html $_(
"the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number"
)}
</span> </span>
{/if} {/if}
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label <label
for="email" for="email"
class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label> class="block text-sm font-medium text-gray-700"
>{$_("e-mail-adress")}</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder={$_('e-mail-adress')} placeholder={$_("e-mail-adress")}
class:border-red-500={!isEmailValidOrEmpty} class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty} class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty} class:focus:ring-red-500={!isEmailValidOrEmpty}
@@ -272,11 +299,13 @@
bind:this={email_input} bind:this={email_input}
type="email" type="email"
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 !isEmailValidOrEmpty} {#if !isEmailValidOrEmpty}
<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>
@@ -287,19 +316,22 @@
id="comments" id="comments"
name="comments" name="comments"
type="checkbox" type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" /> class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label <label for="comments" class="font-medium text-gray-700"
for="comments" >{$_("receipt-needed")}</label
class="font-medium text-gray-700">{$_('receipt-needed')}</label> >
</div> </div>
</div> </div>
{#if address_checked === true} {#if address_checked === true}
<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"
@@ -310,34 +342,41 @@
bind:this={address_input1} bind:this={address_input1}
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={address_input2_value} bind:value={address_input2_value}
bind:this={address_input2} bind:this={address_input2}
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" for="zipcode"
class="block text-sm font-medium text-gray-700">{$_('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}
@@ -345,18 +384,22 @@
bind:this={address_zipcode} bind:this={address_zipcode}
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" for="city"
class="block text-sm font-medium text-gray-700">City</label> class="block text-sm font-medium text-gray-700"
>City</label
>
<input <input
autocomplete="off" autocomplete="off"
placeholder="City" placeholder="City"
@@ -367,11 +410,13 @@
bind:this={address_city} bind:this={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>
@@ -386,16 +431,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,7 +1,7 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick"; import { clickOutside } from "../base/outsideclick";
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";
@@ -13,60 +13,61 @@
dispatch("cancelDelete", { id: delete_donor.id }); dispatch("cancelDelete", { id: delete_donor.id });
} }
function deleteDonor() { function deleteDonor() {
DonorService.donorControllerRemove( dispatch("delete", { id: delete_donor.id });
delete_donor.id,
true
)
.then((resp) => {
Toastify({
text: $_('donor-deleted'),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {});
} }
</script> </script>
{#if modal_open} {#if modal_open}
<div <div
class="fixed z-10 inset-0 overflow-y-auto" class="fixed z-10 inset-0 overflow-y-auto"
use:clickOutside use:clickOutside
on:click_outside={cancelDelete}> on:click_outside={cancelDelete}
>
<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 class="h-6 w-6 text-blue-600" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M9.33 11.5h2.17A4.5 4.5 0 0116 16H9v1h8v-1a5.58 5.58 0 00-.89-3H19a5 5 0 014.52 2.85A13.15 13.15 0 0113 21c-2.76 0-5.1-.59-7-1.63v-9.3a6.97 6.97 0 013.33 1.43zM5 19a1 1 0 01-1 1H2a1 1 0 01-1-1v-9a1 1 0 011-1h2a1 1 0 011 1v9zM18 5a3 3 0 110 6 3 3 0 010-6zm-7-3a3 3 0 110 6 3 3 0 010-6z"/></svg> >
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" /><path
d="M9.33 11.5h2.17A4.5 4.5 0 0116 16H9v1h8v-1a5.58 5.58 0 00-.89-3H19a5 5 0 014.52 2.85A13.15 13.15 0 0113 21c-2.76 0-5.1-.59-7-1.63v-9.3a6.97 6.97 0 013.33 1.43zM5 19a1 1 0 01-1 1H2a1 1 0 01-1-1v-9a1 1 0 011-1h2a1 1 0 011 1v9zM18 5a3 3 0 110 6 3 3 0 010-6zm-7-3a3 3 0 110 6 3 3 0 010-6z"
/></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">
{$_('attention')} {$_("attention")}
</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">
{$_( {$_(
'do-you-want-to-delete-this-donor-with-all-related-donations' "do-you-want-to-delete-this-donor-with-all-related-donations"
)} )}
<br /> <br />
{$_('all-associated-donations-will-get-deleted-as-well')} {$_("all-associated-donations-will-get-deleted-as-well")}
</p> </p>
</div> </div>
</div> </div>
@@ -76,14 +77,16 @@
<button <button
on:click={deleteDonor} on:click={deleteDonor}
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
{$_('confirm-delete-donor-with-all-donations')} >
{$_("confirm-delete-donor-with-all-donations")}
</button> </button>
<button <button
on:click={cancelDelete} on:click={cancelDelete}
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-keep-donor')} >
{$_("cancel-keep-donor")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,14 @@
<script>
import { _ } from "svelte-i18n";
export let address;
</script>
{#if !address || !address.address1}
{$_("no-address")}
{:else}
{address.address1}<br />
<!-- {address.address2 || ''}<br /> -->
{address.postalcode}
{address.city}
{address.country}
{/if}

View File

@@ -0,0 +1,29 @@
<script>
import { _ } from "svelte-i18n";
export let donations;
</script>
{#if !donations || donations.length == 0}
{$_('donor-has-no-associated-donations')}
{:else}
{#each donations as donation}
{#if donation.responseType === "DISTANCEDONATION"}
<a
href="../donations/{donation.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-600 text-white mr-1"
>{donation.runner.firstname}
{donation.runner.middlename || ""}
{donation.runner.lastname}</a
>
{:else}
<a
href="../donations/{donation.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1"
>{$_("fixed-donation")}:
{(donation.amount / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}</a
>
{/if}
{/each}
{/if}

View File

@@ -5,50 +5,73 @@
import DonorsOverview from "./DonorsOverview.svelte"; import DonorsOverview from "./DonorsOverview.svelte";
$: current_donors = []; $: current_donors = [];
export let modal_open = false; export let modal_open = false;
let addDonors;
</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">
{$_('donors')} {$_("donors")}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR: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-donor')} >
{$_("add-donor")}
</button> </button>
{/if} {/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:GET")}
<button <button
on:click={() => { on:click={() => {
const data = (current_donors.filter(d=>d.receiptNeeded===true)).map(function (d) { const data = current_donors
d.address.address2=d.address.address2===""?"":" "+d.address.address2; .filter((d) => d.receiptNeeded === true)
const address=`${d.address.address1}${d.address.address2}, ${d.address.postalcode} ${d.address.city}, ${d.address.country}`; .map(function (d) {
return [d.firstname,d.middlename,d.lastname,d.paidDonationAmount,address]; d.address.address2 =
}) d.address.address2 === "" ? "" : " " + d.address.address2;
let csv = `${$_('csv_import__firstname')};${$_('csv_import__middlename')};${$_('csv_import__lastname')};${$_('total_donation_amount_in_eur')};${$_('address')}\n`; const address = `${d.address.address1}${d.address.address2}, ${d.address.postalcode} ${d.address.city}, ${d.address.country}`;
data.forEach(function(row) { return [
csv += row.join(';'); d.firstname,
d.middlename,
d.lastname,
d.paidDonationAmount,
address,
];
});
let csv = `${$_("csv_import__firstname")};${$_(
"csv_import__middlename"
)};${$_("csv_import__lastname")};${$_(
"total_donation_amount_in_eur"
)};${$_("address")}\n`;
data.forEach(function (row) {
csv += row.join(";");
csv += "\n"; csv += "\n";
}); });
let hiddenElement = document.createElement('a'); let hiddenElement = document.createElement("a");
hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv); hiddenElement.href = "data:text/csv;charset=utf-8," + encodeURI(csv);
hiddenElement.target = '_blank'; hiddenElement.target = "_blank";
hiddenElement.download = `${$_('filename_sponsoringquittungsliste')}.csv`; hiddenElement.download = `${$_(
hiddenElement.click(); "filename_sponsoringquittungsliste"
hiddenElement.remove(); )}.csv`;
hiddenElement.click();
hiddenElement.remove();
}} }}
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"
{$_('sponsoring-quittungs-liste_herunterladen')} >
{$_("sponsoring-quittungs-liste_herunterladen")}
</button> </button>
{/if} {/if}
</span> </span>
<DonorsOverview bind:current_donors /> <DonorsOverview bind:current_donors bind:addDonors />
</section> </section>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:CREATE')} {#if store.state.jwtinfo.userdetails.permissions.includes("DONOR:CREATE")}
<AddDonorModal bind:current_donors bind:modal_open /> <AddDonorModal
on:created={(event) => {
addDonors(event.detail.donors);
}}
bind:modal_open
/>
{/if} {/if}

View File

@@ -5,214 +5,255 @@
import DonorsEmptyState from "./DonorsEmptyState.svelte"; import DonorsEmptyState from "./DonorsEmptyState.svelte";
import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte"; import ConfirmDonorDeletion from "./ConfirmDonorDeletion.svelte";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import TableBottom from "../shared/TableBottom.svelte";
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table";
import { writable } from "svelte/store";
import { onMount } from "svelte";
import InputElement from "../shared/InputElement.svelte";
import TableHeader from "../shared/TableHeader.svelte";
import TableActions from "../shared/TableActions.svelte";
import DonorAddress from "./DonorAddress.svelte";
import DonorDonations from "./DonorDonations.svelte";
import { filterAddress, filterName } from "../shared/tablefilters";
$: searchvalue = ""; $: searchvalue = "";
$: active_deletes = []; $: active_deletes = [];
$: current_donations = []; $: selectedDonors =
let modal_open = false; $table?.getSelectedRowModel().rows.map((row) => row.original) || [];
let delete_donor = {}; $: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: dataLoaded = false;
export let current_donors = []; export let current_donors = [];
const donors_promise = DonorService.donorControllerGetAll().then((val) => { export const addDonors = (donors) => {
current_donors = val; current_donors = current_donors.concat(...donors);
options.update((options) => ({
...options,
data: current_donors,
}));
};
//Section table
const columns = [
{
accessorKey: "id",
header: () => "id",
filterFn: `equalsString`,
},
{
accessorKey: "name",
header: () => $_("name"),
cell: (info) => {
const d = info.row.original;
if (d.middlename) {
return `${d.firstname} ${d.middlename} ${d.lastname}`;
} else {
return `${d.firstname} ${d.lastname}`;
}
},
filterFn: `name`,
},
{
accessorKey: "address",
header: () => $_("contact-information"),
cell: (info) => {
return renderComponent(DonorAddress, { address: info.getValue() });
},
filterFn: `address`,
},
{
accessorKey: "donations",
header: () => $_("sponsorings"),
cell: (info) => {
return renderComponent(DonorDonations, { donations: info.getValue() });
},
enableColumnFilter: false,
},
{
accessorKey: "donationAmount",
header: () => $_("total-donation-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}€`;
},
enableColumnFilter: false,
},
{
accessorKey: "paidDonationAmount",
header: () => $_("total-paid-amount"),
cell: (info) => {
return `${(info.getValue() / 100)
.toFixed(2)
.toLocaleString("de-DE", { valute: "EUR" })}€`;
},
enableColumnFilter: false,
},
{
accessorKey: "actions",
header: () => $_("action"),
cell: (info) => {
return renderComponent(TableActions, {
detailsLink: `./${info.row.original.id}`,
deleteAction: () => {
active_deletes = current_donors.filter(
(r) => r.id == info.row.original.id
);
},
deleteEnabled:
store.state.jwtinfo.userdetails.permissions.includes(
"DONOR:DELETE"
),
});
},
enableColumnFilter: false,
enableSorting: false,
},
];
const options = writable({
data: [],
columns: columns,
initialState: {
pagination: {
pageSize: 50,
},
},
filterFns: {
name: filterName,
address: filterAddress,
},
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
}); });
const donation_promise = DonationService.donationControllerGetAll().then( const table = createSvelteTable(options);
(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("*", ""));
} }
return id.toString() === searchvalue; return id.toString() === searchvalue;
} }
onMount(async () => {
let page = 0;
while (page >= 0) {
const donors = await DonorService.donorControllerGetAll(page, 500);
if (donors.length == 0) {
page = -2;
}
current_donors = current_donors.concat(...donors);
options.update((options) => ({
...options,
data: current_donors,
}));
dataLoaded = true;
page++;
}
console.log("All donors loaded");
});
</script> </script>
<ConfirmDonorDeletion <ConfirmDonorDeletion
on:cancelDelete={(event) => { on:cancelDelete={(event) => {
modal_open = false; active_deletes = active_deletes.filter((a) => a.id !== event.detail.id);
active_deletes[event.detail.id] = false;
}} }}
bind:modal_open on:delete={async (event) => {
bind:delete_donor /> await DonorService.donorControllerRemove(event.detail.id, true);
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:GET')} Toastify({
{#await donors_promise && donation_promise} text: $_("donor-deleted"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_donors = current_donors.filter((d) => d.id !== event.detail.id);
active_deletes = active_deletes.filter((a) => a.id !== event.detail.id);
options.update((options) => ({
...options,
data: current_donors,
}));
}}
modal_open={active_deletes.length > 0}
delete_donor={active_deletes[0]}
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("DONOR: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">{$_('donors-are-being-loaded')}</p> >
<p class="text-sm">{$_('this-might-take-a-moment')}</p> <p class="font-bold">{$_("donors-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:then} {:else if current_donors.length === 0}
{#if current_donors.length === 0} <DonorsEmptyState />
<DonorsEmptyState /> {: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="w-full">
<tr> <thead>
<th {#each $table.getHeaderGroups() as headerGroup}
scope="col" <tr class="select-none">
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
{$_('name')} <InputElement
</th> type="checkbox"
<th checked={$table.getIsAllRowsSelected()}
scope="col" indeterminate={$table.getIsSomeRowsSelected()}
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> on:change={() => $table.toggleAllRowsSelected()}
{$_('contact-information')} />
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('donations')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('total-donation-amount')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('total-paid-amount')}
</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_donors as donor} <tbody>
{#if donor.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
) || donor.lastname type="checkbox"
.toLowerCase() checked={row.getIsSelected()}
.includes( on:change={() => row.toggleSelected()}
searchvalue.toLowerCase() />
) || should_display_based_on_id(donor.id)} </td>
<tr data-rowid="donor_{donor.id}"> {#each row.getVisibleCells() as cell}
<td class="px-6 py-4 whitespace-nowrap"> <td>
<div class="flex items-center"> <svelte:component
<div class="ml-4"> this={flexRender(
<div class="text-sm font-medium text-gray-900"> cell.column.columnDef.cell,
{donor.firstname} cell.getContext()
{donor.middlename || ''} )}
{donor.lastname} />
</div> </td>
</div> {/each}
</div> </tr>
</td> {/each}
<td class="px-6 py-4 whitespace-nowrap"> </tbody>
{#if donor.email} </table>
<div class="text-sm text-gray-500">{donor.email}</div>
{/if}
{#if donor.phone}
<div class="text-sm text-gray-500">{donor.phone}</div>
{/if}
{#if donor.address.address1 !== null}
{donor.address.address1}<br />
{donor.address.address2 || ''}<br />
{donor.address.postalcode}
{donor.address.city}
{donor.address.country}
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if current_donations.filter((d) => d.donor.id == donor.id).length > 0}
{#each current_donations.filter((o) => o.donor.id == donor.id) as d}
{#if d.responseType === 'DISTANCEDONATION'}
<a
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}
{d.runner.middlename || ''}
{d.runner.lastname}</a>
{:else}
<a
href="../donations/{d.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-700 text-white mr-1">{$_('fixed-donation')}:
{(d.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}</a>
{/if}
{/each}
{:else}{$_('donor-has-no-associated-donations')}{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{(donor.donationAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{(donor.paidDonationAmount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })}
</td>
{#if active_deletes[donor.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[donor.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
DonorService.donorControllerRemove(donor.id, false)
.then((resp) => {
current_donors = current_donors.filter((obj) => obj.id !== donor.id);
Toastify({
text: 'Donor deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_donor = donor;
});
}}
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="./{donor.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('details')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('DONOR:DELETE')}
<button
on:click={() => {
active_deletes[donor.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="h-2" />
<TableBottom {table} {selected} />
{/if}
{/if} {/if}

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

@@ -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,35 +302,34 @@
</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">
@@ -329,43 +339,51 @@
id="toggle_selfservice_feature" id="toggle_selfservice_feature"
name="toggle_selfservice_feature" 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="toggle_selfservice_feature" 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}
@@ -378,12 +396,14 @@
id="toggle_address_checkbox" id="toggle_address_checkbox"
name="toggle_address_checkbox" 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="toggle_address_checkbox" 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,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

@@ -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) => {
// //

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

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

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,7 +1,9 @@
<script> <script>
import { _ } from "svelte-i18n"; import {
let tablePageCount = [50, 100, 250, 500]; RunnerOrganizationService,
import { writable } from "svelte/store"; RunnerService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import { import {
createSvelteTable, createSvelteTable,
flexRender, flexRender,
@@ -9,11 +11,46 @@
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
renderComponent,
} from "@tanstack/svelte-table"; } from "@tanstack/svelte-table";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
import { writable } from "svelte/store";
import store from "../../store";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
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 = $: selected =
$table?.getSelectedRowModel().rows.map((row) => row.index) || []; $table?.getSelectedRowModel().rows.map((row) => row.index) || [];
$: active_delete = undefined;
let dataLoaded = false;
export let current_runners = [];
$: sponsoring_contracts_show = selected.length > 0;
$: cards_show = selected.length > 0;
$: certificates_show = selected.length > 0;
$: teams = [];
$: orgs = [];
export const addRunners = (runners) => {
current_runners = current_runners.concat(...runners);
options.update((options) => ({
...options,
data: current_runners,
}));
};
//Section table
const columns = [ const columns = [
{ {
accessorKey: "id", accessorKey: "id",
@@ -22,71 +59,79 @@
}, },
{ {
accessorKey: "firstname", accessorKey: "firstname",
header: () => $_('first-name'), header: () => $_("first-name"),
filterFn: `includesString`,
},
{
accessorKey: "middlename",
header: () => $_("middle-name"),
cell: (info) => {
if (!info || !info.getValue()) {
return "";
}
return info.getValue();
},
filterFn: `includesString`, filterFn: `includesString`,
}, },
{ {
accessorKey: "lastname", accessorKey: "lastname",
header: () => $_('last-name'), header: () => $_("last-name"),
filterFn: `includesString`, filterFn: `includesString`,
}, },
{ {
accessorKey: "group", accessorKey: "group",
header: () => $_('group'), header: () => $_("group"),
cell: (info) => { cell: (info) => {
const group = info.getValue(); const group = info.getValue();
if(group.responseType === "RUNNERORGANIZATION"){return group.name} if (group.responseType === "RUNNERORGANIZATION") {
return `${group.parentGroup.name} > ${group.name}` return group.name;
}
return `${group.parentGroup.name} > ${group.name}`;
}, },
filterFn: `includesString`, filterFn: `group`,
}, },
{ {
accessorKey: "distance", accessorKey: "distance",
header: () => $_('distance'), header: () => $_("distance"),
cell: (info) => { cell: (info) => {
if(info.getValue() < 1000){return `${info.getValue()} m`} if (info.getValue() < 1000) {
return `${(info.getValue() / 1000).toFixed(1)} km` return `${info.getValue()} m`;
}
return `${(info.getValue() / 1000).toFixed(1)} km`;
}, },
enableColumnFilter: false, 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,
},
]; ];
//
import {
RunnerService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import store from "../../store";
import GenerateSponsoringContracts from "../pdf_generation/GenerateSponsoringContracts.svelte";
import GenerateRunnerCards from "../pdf_generation/GenerateRunnerCards.svelte";
import GenerateRunnerCertificates from "../pdf_generation/GenerateRunnerCertificates.svelte";
import { onMount } from "svelte";
import InputElement from "../shared/InputElement.svelte";
$: active_deletes = [];
let dataLoaded = false;
export let current_runners = [];
$: sponsoring_contracts_show = generate_runners.length > 0;
$: cards_show = generate_runners.length > 0;
$: certificates_show = generate_runners.length > 0;
$: generate_runners = []; //current_runners.filter((r) => r.selected === true);
$: teams = [];
$: 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);
const options = writable({ const options = writable({
data: [], data: [],
columns: columns, columns: columns,
filterFns: {
group: groupFilter,
},
initialState: { initialState: {
pagination: { pagination: {
pageSize: tablePageCount[0], pageSize: 50,
}, },
}, },
enableRowSelection: true, enableRowSelection: true,
@@ -96,16 +141,22 @@
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
}); });
const table = createSvelteTable(options); const table = createSvelteTable(options);
onMount(() => {
RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val;
dataLoaded = true;
options.update((options) => ({ async function deleteRunner(delete_runner_id) {
...options, await RunnerService.runnerControllerRemove(delete_runner_id, true);
data: current_runners, current_runners = current_runners.filter((r) => r.id !== delete_runner_id);
})); options.update((options) => ({
}); ...options,
data: current_runners,
}));
Toastify({
text: $_("runner-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
onMount(async () => {
RunnerTeamService.runnerTeamControllerGetAll().then((val) => { RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val; teams = val;
}); });
@@ -114,9 +165,34 @@
orgs = 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>
<DeleteRunnerModal
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 store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")}
{#if !dataLoaded} {#if !dataLoaded}
<div <div
@@ -127,193 +203,65 @@
<p class="text-sm">{$_("this-might-take-a-moment")}</p> <p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div> </div>
{:else} {:else}
<div class="h-12"> <div class="h-12 mt-2">
<GenerateSponsoringContracts <GenerateSponsoringContracts
bind:sponsoring_contracts_show bind:sponsoring_contracts_show
bind:generate_runners bind:generate_runners={selectedRunners}
/>
<GenerateRunnerCards
bind:cards_show
bind:generate_runners={selectedRunners}
/> />
<GenerateRunnerCards bind:cards_show bind:generate_runners />
<GenerateRunnerCertificates <GenerateRunnerCertificates
bind:certificates_show bind:certificates_show
bind:generate_runners bind:generate_runners={selectedRunners}
/> />
</div> </div>
<table class="w-full"> <div class="overflow-x-auto">
<thead> <table class="w-full">
{#each $table.getHeaderGroups() as headerGroup} <thead>
<tr class="select-none"> {#each $table.getHeaderGroups() as headerGroup}
<th class="inset-y-0 left-0 px-4 py-2 text-left bg-stone-100 w-px"> <tr class="select-none">
<InputElement <th class="inset-y-0 left-0 px-4 py-2 text-left w-px">
type="checkbox" <InputElement
checked={$table.getIsAllRowsSelected()} type="checkbox"
indeterminate={$table.getIsSomeRowsSelected()} checked={$table.getIsAllRowsSelected()}
on:change={() => $table.toggleAllRowsSelected()} indeterminate={$table.getIsSomeRowsSelected()}
/> on:change={() => $table.toggleAllRowsSelected()}
</th> />
{#each headerGroup.headers as header}
<th class="cursor-pointer">
{#if !header.isPlaceholder}
<div on:click={header.column.getToggleSortingHandler()}>
<svelte:component
this={flexRender(
header.column.columnDef.header,
header.getContext()
)}
/>
{#if header.column.getIsSorted().toString() == "asc"}
🔼
{:else if header.column.getIsSorted().toString() == "desc"}
🔽
{/if}
</div>
{/if}
{#if header.column.getCanFilter()}
<div class="relative max-w-xs">
<input
title="name-search"
value={header.column.getFilterValue() || ""}
on:keyup={(e) => {
header.column.setFilterValue(e.target.value);
}}
type="text"
class="block rounded-md border-gray-200 py-2 pl-8 text-xs focus:border-blue-500 focus:ring-blue-500"
placeholder=""
/>
<div
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2"
>
<svg
class="h-3.5 w-3.5 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
width={16}
height={16}
fill="currentColor"
viewBox="0 0 16 16"
>
<path
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
/>
</svg>
</div>
</div>
{/if}
</th> </th>
{/each} {#each headerGroup.headers as header}
</tr> <TableHeader {header} />
{/each} {/each}
</thead> </tr>
<tbody> {/each}
{#each $table.getRowModel().rows as row} </thead>
<tr> <tbody>
<td class="inset-y-0 left-0 px-4 py-2"> {#each $table.getRowModel().rows as row}
<InputElement <tr>
type="checkbox" <td class="inset-y-0 left-0 px-4 py-2 text-center w-px">
checked={row.getIsSelected()} <InputElement
on:change={() => row.toggleSelected()} type="checkbox"
/> checked={row.getIsSelected()}
</td> on:change={() => row.toggleSelected()}
{#each row.getVisibleCells() as cell}
<td>
<svelte:component
this={flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
/> />
</td> </td>
{/each} {#each row.getVisibleCells() as cell}
</tr> <td>
{/each} <svelte:component
</tbody> this={flexRender(
</table> cell.column.columnDef.cell,
cell.getContext()
)}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
<div class="h-2" /> <div class="h-2" />
<div class="flex items-center gap-2">
<button
class="border rounded p-1"
on:click={() => $table.setPageIndex(0)}
disabled={!$table.getCanPreviousPage()}
>
{"<<"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.previousPage()}
disabled={!$table.getCanPreviousPage()}
>
{"<"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.nextPage()}
disabled={!$table.getCanNextPage()}
>
{">"}
</button>
<button
class="border rounded p-1"
on:click={() => $table.setPageIndex($table.getPageCount() - 1)}
disabled={!$table.getCanNextPage()}
>
{">>"}
</button>
<span class="flex items-center gap-1">
<div>Page</div>
<strong>
{$table.getState().pagination.pageIndex + 1} of{" "}
{$table.getPageCount()}
</strong>
</span>
<span class="flex items-center gap-1">
| Go to page:
<input
type="number"
defaultValue={$table.getState().pagination.pageIndex + 1}
on:change={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
$table.setPageIndex(page);
}}
class="border p-1 rounded w-16"
/>
</span>
<select
value={$table.getState().pagination.pageSize}
on:change={(e) => {
const ps = Number(e.target.value);
console.log({ ps });
$table.setPageSize(Number(e.target.value));
}}
>
{#each tablePageCount as pageSize}
<option value={pageSize}>{pageSize}</option>
{/each}
</select>
</div>
<pre>{JSON.stringify($table.getState(), null, 2)}</pre>
<div>
{Object.keys(selected).length} of{" "}
{$table.getPreFilteredRowModel().rows.length} Total Rows Selected
</div>
{/if} {/if}
{/if} {/if}
<TableBottom {table} {selected} />
<style>
thead {
background: #fff;
}
thead {
position: sticky;
inset-block-start: 0;
}
tbody td {
padding: 4px;
}
tbody tr:nth-child(even) {
background: #fafafa;
}
tbody tr {
transition: all, 0.2s;
}
tbody tr:hover {
background: #f5f5f5;
}
</style>

View File

@@ -8,8 +8,8 @@
} 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) =>
@@ -18,6 +18,7 @@
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) => {
// //

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,21 +1,46 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { DataHandler, Datatable, Th, ThFilter } from "@vincjo/datatables";
import { ScanService, TrackService } from "@odit/lfk-client-js"; import { ScanService, TrackService } 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";
import ThFilterRunner from "./ThFilterRunner.svelte"; import InputElement from "../shared/InputElement.svelte";
import ThFilterTrack from "./ThFilterTrack.svelte"; import TableActions from "../shared/TableActions.svelte";
$: active_deletes = []; 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 handler = new DataHandler(current_scans, { rowsPerPage: 20 }); export const addScans = (scans) => {
const rows = handler.getRows(); current_scans = current_scans.concat(...scans);
const scans_promise = ScanService.scanControllerGetAll().then((val) => { options.update((options) => ({
current_scans = val; ...options,
handler.setRows(val); data: current_scans,
}); }));
$: allTracks = []; };
let allTracks = [];
TrackService.trackControllerGetAll().then((val) => { TrackService.trackControllerGetAll().then((val) => {
allTracks = val; allTracks = val;
}); });
@@ -39,10 +64,148 @@
Math.floor(laptime / 60) * 60 Math.floor(laptime / 60) * 60
}`; }`;
} }
const columns = [
{
accessorKey: "id",
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>
<DeleteScanModal
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 store.state.jwtinfo.userdetails.permissions.includes("SCAN:GET")}
{#await scans_promise} {#if !dataLoaded}
<div <div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2" class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert" role="alert"
@@ -50,184 +213,96 @@
<p class="font-bold">{$_("scans-are-being-loaded")}</p> <p class="font-bold">{$_("scans-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_scans.length === 0}
{#if current_scans.length === 0} <ScansEmptyState />
<ScansEmptyState /> {:else}
{:else} {#if selected.length > 0}
<div <button
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll" 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 inline-flex"
id="options-menu"
on:click={async () => {
const prom = [];
for (const scan of selectedScans) {
prom.push(ScanService.scanControllerRemove(scan.id, true));
}
await Promise.all(prom);
for (const scan of selectedScans) {
current_scans = current_scans.filter((r) => r.id !== scan.id);
}
options.update((options) => ({
...options,
data: current_scans,
}));
$table.resetRowSelection();
Toastify({
text: $_("scan-deleted"),
duration: 3500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}}
> >
<Datatable {handler}> {$_("delete-scans")}
<table class="divide-y divide-gray-200 w-full"> <svg
<thead class="bg-gray-50"> xmlns="http://www.w3.org/2000/svg"
<tr> fill="none"
<Th {handler} orderBy="id">ID</Th> viewBox="0 0 24 24"
<Th {handler}> stroke-width="1.5"
{$_("runner")} stroke="currentColor"
</Th> class="w-5 h-5"
<Th {handler}> >
{$_("distance")} <path
</Th> stroke-linecap="round"
<Th {handler}> stroke-linejoin="round"
{$_("track")} 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"
</Th> />
<Th {handler}> </svg>
{$_("laptime")} </button>
</Th>
<Th {handler}>
{$_("status")}
</Th>
<th
scope="col"
class="relative px-6 py-3"
style="border-bottom: 1px solid #ddd;"
>
{$_("action")}
</th>
</tr>
<tr>
<ThFilter {handler} filterBy="id" />
<ThFilterRunner {handler} />
<th style="border-bottom: 1px solid #ddd;" />
<ThFilterTrack tracks={allTracks} {handler} />
<!-- <th style="border-bottom: 1px solid #ddd;" /> -->
<th style="border-bottom: 1px solid #ddd;" />
<th style="border-bottom: 1px solid #ddd;" />
<!-- TODO: filter status -->
<th style="border-bottom: 1px solid #ddd;" />
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each $rows as scan}
<tr data-rowid="scan_{scan.id}">
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{scan.id}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<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 text-left">
<div class="text-sm font-medium text-gray-900">
{#if scan.distance < 1000}
{scan.distance}m
{:else}{scan.distance / 1000}km{/if}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-left">
<div class="text-sm font-medium text-gray-900">
{#if scan.track}
<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 text-left">
{#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>
{/each}
</tbody>
</table>
</Datatable>
</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

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

View File

@@ -1,14 +1,18 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
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" > <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

@@ -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,65 @@
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);
return filterRunner(runner, value)
};
export const donationRunnerFilter = (row, columnId, value) => {
const runner = row.getValue(columnId);
if (!runner) { return false; }
return filterRunner(runner, value)
};
export const donationDonorFilter = (row, columnId, value) => {
const runner = row.getValue(columnId);
return filterRunner(runner, value)
};
function filterRunner(runner, value) {
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 filterName = (row, columnId, value) => {
const obj = row.original;
if (obj.middlename) {
return `${obj.firstname} ${obj.middlename} ${obj.lastname}`.toLowerCase().includes(value.toLowerCase())
}
return `${obj.firstname} ${obj.lastname}`.toLowerCase().includes(value.toLowerCase())
};
export const filterAddress = (row, columnId, value) => {
const obj = row.original.address;
if (obj.address2) {s
return `${obj.address1} ${obj.address2} ${obj.postalcode} ${obj.city} ${obj.country}`.toLowerCase().includes(value.toLowerCase())
}
return `${obj.address1} ${obj.postalcode} ${obj.city} ${obj.country}`.toLowerCase().includes(value.toLowerCase())
};
export const statusFilter = (row, columnId, value) => {
const status = row.getValue(columnId);
return status.toString().includes(value);
};

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 { 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'),

View File

@@ -1,6 +1,6 @@
<script> <script>
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { tick, createEventDispatcher } from "svelte"; import { tick, createEventDispatcher } from "svelte";
export let copy_modal_open; export let copy_modal_open;
@@ -46,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" > <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-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-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_client.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

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

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

@@ -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={() => {

View File

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